diff --git a/.docker/frankenphp/Caddyfile b/.docker/frankenphp/Caddyfile new file mode 100644 index 00000000..83839304 --- /dev/null +++ b/.docker/frankenphp/Caddyfile @@ -0,0 +1,55 @@ +{ + {$CADDY_GLOBAL_OPTIONS} + + frankenphp { + {$FRANKENPHP_CONFIG} + } + + # https://caddyserver.com/docs/caddyfile/directives#sorting-algorithm + order mercure after encode + order vulcain after reverse_proxy + order php_server before file_server +} + +{$CADDY_EXTRA_CONFIG} + +{$SERVER_NAME:localhost} { + log { + # Redact the authorization query parameter that can be set by Mercure + format filter { + wrap console + fields { + uri query { + replace authorization REDACTED + } + } + } + } + + root * /app/public + encode zstd br gzip + + mercure { + # Transport to use (default to Bolt) + transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db} + # Publisher JWT key + publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG} + # Subscriber JWT key + subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG} + # Allow anonymous subscribers (double-check that it's what you want) + anonymous + # Enable the subscription API (double-check that it's what you want) + subscriptions + # Extra directives + {$MERCURE_EXTRA_DIRECTIVES} + } + + vulcain + + {$CADDY_SERVER_EXTRA_DIRECTIVES} + + # Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics + header ?Permissions-Policy "browsing-topics=()" + + php_server +} diff --git a/.docker/frankenphp/conf.d/app.dev.ini b/.docker/frankenphp/conf.d/app.dev.ini new file mode 100644 index 00000000..e50f43d0 --- /dev/null +++ b/.docker/frankenphp/conf.d/app.dev.ini @@ -0,0 +1,5 @@ +; See https://docs.docker.com/desktop/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host +; See https://github.com/docker/for-linux/issues/264 +; The `client_host` below may optionally be replaced with `discover_client_host=yes` +; Add `start_with_request=yes` to start debug session on each request +xdebug.client_host = host.docker.internal diff --git a/.docker/frankenphp/conf.d/app.ini b/.docker/frankenphp/conf.d/app.ini new file mode 100644 index 00000000..10a062f2 --- /dev/null +++ b/.docker/frankenphp/conf.d/app.ini @@ -0,0 +1,18 @@ +expose_php = 0 +date.timezone = UTC +apc.enable_cli = 1 +session.use_strict_mode = 1 +zend.detect_unicode = 0 + +; https://symfony.com/doc/current/performance.html +realpath_cache_size = 4096K +realpath_cache_ttl = 600 +opcache.interned_strings_buffer = 16 +opcache.max_accelerated_files = 20000 +opcache.memory_consumption = 256 +opcache.enable_file_override = 1 + +memory_limit = 256M + +upload_max_filesize=256M +post_max_size=300M \ No newline at end of file diff --git a/.docker/frankenphp/conf.d/app.prod.ini b/.docker/frankenphp/conf.d/app.prod.ini new file mode 100644 index 00000000..3bcaa71e --- /dev/null +++ b/.docker/frankenphp/conf.d/app.prod.ini @@ -0,0 +1,2 @@ +opcache.preload_user = root +opcache.preload = /app/config/preload.php diff --git a/.docker/frankenphp/docker-entrypoint.sh b/.docker/frankenphp/docker-entrypoint.sh new file mode 100644 index 00000000..56b3bc31 --- /dev/null +++ b/.docker/frankenphp/docker-entrypoint.sh @@ -0,0 +1,82 @@ +#!/bin/sh +set -e + +if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then + # Install the project the first time PHP is started + # After the installation, the following block can be deleted + if [ ! -f composer.json ]; then + rm -Rf tmp/ + composer create-project "symfony/skeleton $SYMFONY_VERSION" tmp --stability="$STABILITY" --prefer-dist --no-progress --no-interaction --no-install + + cd tmp + cp -Rp . .. + cd - + rm -Rf tmp/ + + composer require "php:>=$PHP_VERSION" runtime/frankenphp-symfony + composer config --json extra.symfony.docker 'true' + + if grep -q ^DATABASE_URL= .env; then + echo "To finish the installation please press Ctrl+C to stop Docker Compose and run: docker compose up --build -d --wait" + sleep infinity + fi + fi + + if [ -z "$(ls -A 'vendor/' 2>/dev/null)" ]; then + composer install --prefer-dist --no-progress --no-interaction + fi + + # Install additional composer packages if COMPOSER_EXTRA_PACKAGES is set + if [ -n "$COMPOSER_EXTRA_PACKAGES" ]; then + echo "Installing additional composer packages: $COMPOSER_EXTRA_PACKAGES" + # Note: COMPOSER_EXTRA_PACKAGES is intentionally not quoted to allow word splitting + # This enables passing multiple package names separated by spaces + # shellcheck disable=SC2086 + composer require $COMPOSER_EXTRA_PACKAGES --no-install --no-interaction --no-progress + if [ $? -eq 0 ]; then + echo "Running composer install to install packages without dev dependencies..." + composer install --no-dev --no-interaction --no-progress --optimize-autoloader + if [ $? -eq 0 ]; then + echo "Successfully installed additional composer packages" + else + echo "Failed to install composer dependencies" + exit 1 + fi + else + echo "Failed to add additional composer packages to composer.json" + exit 1 + fi + fi + + if grep -q ^DATABASE_URL= .env; then + echo "Waiting for database to be ready..." + ATTEMPTS_LEFT_TO_REACH_DATABASE=60 + until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(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 + + if [ "$( find ./migrations -iname '*.php' -print -quit )" ]; then + php bin/console doctrine:migrations:migrate --no-interaction + fi + fi + + setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var + setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var +fi + +exec docker-php-entrypoint "$@" \ No newline at end of file diff --git a/.docker/frankenphp/worker.Caddyfile b/.docker/frankenphp/worker.Caddyfile new file mode 100644 index 00000000..d384ae4c --- /dev/null +++ b/.docker/frankenphp/worker.Caddyfile @@ -0,0 +1,4 @@ +worker { + file ./public/index.php + env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime +} diff --git a/.docker/partdb-entrypoint.sh b/.docker/partdb-entrypoint.sh index 3e06256a..61e8d1e6 100644 --- a/.docker/partdb-entrypoint.sh +++ b/.docker/partdb-entrypoint.sh @@ -39,8 +39,72 @@ if [ -d /var/www/html/var/db ]; then fi fi -# Start PHP-FPM -service php8.1-fpm start +# Install additional composer packages if COMPOSER_EXTRA_PACKAGES is set +if [ -n "$COMPOSER_EXTRA_PACKAGES" ]; then + echo "Installing additional composer packages: $COMPOSER_EXTRA_PACKAGES" + # Note: COMPOSER_EXTRA_PACKAGES is intentionally not quoted to allow word splitting + # This enables passing multiple package names separated by spaces + # shellcheck disable=SC2086 + sudo -E -u www-data composer require $COMPOSER_EXTRA_PACKAGES --no-install --no-interaction --no-progress + if [ $? -eq 0 ]; then + echo "Running composer install to install packages without dev dependencies..." + sudo -E -u www-data composer install --no-dev --no-interaction --no-progress --optimize-autoloader + if [ $? -eq 0 ]; then + echo "Successfully installed additional composer packages" + else + echo "Failed to install composer dependencies" + exit 1 + fi + else + echo "Failed to add additional composer packages to composer.json" + exit 1 + fi +fi + +# Start PHP-FPM (the PHP_VERSION is replaced by the configured version in the Dockerfile) +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 +112,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 1527286c..aa88eef2 100644 --- a/.docker/symfony.conf +++ b/.docker/symfony.conf @@ -24,28 +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 - PassEnv TRUSTED_PROXIES TRUSTED_HOSTS LOCK_DSN - PassEnv DATABASE_URL ENFORCE_CHANGE_COMMENTS_FOR - PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI CHECK_FOR_UPDATES - 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_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 - - 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 - # 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/.dockerignore b/.dockerignore index 8929729c..472b1bb3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,6 +5,8 @@ tests/ docs/ .git +/public/media/* + ###> symfony/framework-bundle ### /.env.local /.env.local.php @@ -42,3 +44,39 @@ yarn-error.log /phpunit.xml .phpunit.result.cache ###< phpunit/phpunit ### + + +### From frankenphp + +**/*.log +**/*.php~ +**/*.dist.php +**/*.dist +**/*.cache +**/._* +**/.dockerignore +**/.DS_Store +**/.git/ +**/.gitattributes +**/.gitignore +**/.gitmodules +**/compose.*.yaml +**/compose.*.yml +**/compose.yaml +**/compose.yml +**/docker-compose.*.yaml +**/docker-compose.*.yml +**/docker-compose.yaml +**/docker-compose.yml +**/Dockerfile +**/Thumbs.db +.github/ +public/bundles/ +var/ +vendor/ +.editorconfig +.env.*.local +.env.local +.env.local.php +.env.test + diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..66990769 --- /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 5b1a73e5..9a6ce846 100644 --- a/.env +++ b/.env @@ -14,39 +14,26 @@ DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db" # Uncomment this line (and comment the line above to use a MySQL database #DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db?serverVersion=5.7 +# Set this value to 1, if you want to use SSL to connect to the MySQL server. It will be tried to use the CA certificate +# otherwise a CA bundle shipped with PHP will be used. +# Leave it at 0, if you do not want to use SSL or if your server does not support it +DATABASE_MYSQL_USE_SSL_CA=0 + +# Set this value to 0, if you don't want to verify the CA certificate of the MySQL server +# Only do this, if you know what you are doing! +DATABASE_MYSQL_SSL_VERIFY_CERT=1 + +# Emulate natural sorting of strings even on databases that do not support it (like SQLite, MySQL or MariaDB < 10.7) +# This can be slow on big databases and might have some problems and quirks, so use it with caution +DATABASE_EMULATE_NATURAL_SORT=0 + ################################################################################### # General settings ################################################################################### -# 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 -# 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 to the website in emails and so on -# This must end with a slash! +# The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates or when no request context is available. 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 ################################################################################### @@ -63,21 +50,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 ################################################################################### @@ -87,68 +59,17 @@ 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 - -################################################################################## -# 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 - ################################################################################### # SAML Single sign on-settings ################################################################################### # Set this to 1 to enable SAML single sign on +# Be also sure to set the correct values for DEFAULT_URI SAML_ENABLED=0 +# Set to 1, if your Part-DB installation is behind a reverse proxy and you want to use SAML +SAML_BEHIND_PROXY=0 + # A JSON encoded array of role mappings in the form { "saml_role": PARTDB_GROUP_ID, "*": PARTDB_GROUP_ID } # The first match is used, so the order is important! Put the group mapping with the most privileges first. # Please not to only use single quotes to enclose the JSON string @@ -188,19 +109,14 @@ DEMO_MODE=0 # In that case all URL contains the index.php front controller in URL NO_URL_REWRITE_AVAILABLE=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 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 +# 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 # Set the trusted IPs here, when using an reverse proxy -#TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 +#TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 #TRUSTED_HOSTS='^(localhost|example\.com)$' @@ -209,3 +125,13 @@ APP_SECRET=a03498528f5a5fc089273ec9ae5b2849 # postgresql+advisory://db_user:db_password@localhost/db_name LOCK_DSN=flock ###< symfony/lock ### + +###> nelmio/cors-bundle ### +CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' +###< nelmio/cors-bundle ### + +###> symfony/framework-bundle ### +APP_ENV=prod +APP_SECRET=a03498528f5a5fc089273ec9ae5b2849 +APP_SHARE_DIR=var/share +###< symfony/framework-bundle ### diff --git a/.env.dev b/.env.dev new file mode 100644 index 00000000..53b05877 --- /dev/null +++ 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 a9b0cccf..9117ff16 100644 --- a/.env.test +++ b/.env.test @@ -5,5 +5,11 @@ SYMFONY_DEPRECATIONS_HELPER=999999 PANTHER_APP_ENV=panther PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots +DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db" # Doctrine automatically adds an _test suffix to database name in test env -DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db \ No newline at end of file +#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 + +INSTANCE_NAME="Part-DB" \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..637a66b5 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,186 @@ +# Copilot Instructions for Part-DB + +Part-DB is an Open-Source inventory management system for electronic components built with Symfony 7.4 and modern web technologies. + +## Technology Stack + +- **Backend**: PHP 8.2+, Symfony 7.4, Doctrine ORM +- **Frontend**: Bootstrap 5, Hotwire Stimulus/Turbo, TypeScript, Webpack Encore +- **Database**: MySQL 5.7+/MariaDB 10.4+/PostgreSQL 10+/SQLite +- **Testing**: PHPUnit with DAMA Doctrine Test Bundle +- **Code Quality**: Easy Coding Standard (ECS), PHPStan (level 5) + +## Project Structure + +- `src/`: PHP application code organized by purpose (Controller, Entity, Service, Form, etc.) +- `assets/`: Frontend TypeScript/JavaScript and CSS files +- `templates/`: Twig templates for views +- `tests/`: PHPUnit tests mirroring the `src/` structure +- `config/`: Symfony configuration files +- `public/`: Web-accessible files +- `translations/`: Translation files for multi-language support + +## Coding Standards + +### PHP Code + +- Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) and [Symfony coding standards](https://symfony.com/doc/current/contributing/code/standards.html) +- Use type hints for all parameters and return types +- Always declare strict types: `declare(strict_types=1);` at the top of PHP files +- Use PHPDoc blocks for complex logic or when type information is needed + +### TypeScript/JavaScript + +- Use TypeScript for new frontend code +- Follow existing Stimulus controller patterns in `assets/controllers/` +- Use Bootstrap 5 components and utilities +- Leverage Hotwire Turbo for dynamic page updates + +### Naming Conventions + +- Entities: Use descriptive names that reflect database models (e.g., `Part`, `StorageLocation`) +- Controllers: Suffix with `Controller` (e.g., `PartController`) +- Services: Descriptive names reflecting their purpose (e.g., `PartService`, `LabelGenerator`) +- Tests: Match the class being tested with `Test` suffix (e.g., `PartTest`, `PartControllerTest`) + +## Development Workflow + +### Dependencies + +- Install PHP dependencies: `composer install` +- Install JS dependencies: `yarn install` +- Build frontend assets: `yarn build` (production) or `yarn watch` (development) + +### Database + +- Create database: `php bin/console doctrine:database:create --env=dev` +- Run migrations: `php bin/console doctrine:migrations:migrate --env=dev` +- Load fixtures: `php bin/console partdb:fixtures:load -n --env=dev` + +Or use Makefile shortcuts: +- `make dev-setup`: Complete development environment setup +- `make dev-reset`: Reset development environment (cache clear + migrate) + +### Testing + +- Set up test environment: `make test-setup` +- Run all tests: `php bin/phpunit` +- Run specific test: `php bin/phpunit tests/Path/To/SpecificTest.php` +- Run tests with coverage: `php bin/phpunit --coverage-html var/coverage` +- Test environment uses SQLite by default for speed + +### Static Analysis + +- Run PHPStan: `composer phpstan` or `COMPOSER_MEMORY_LIMIT=-1 php -d memory_limit=1G vendor/bin/phpstan analyse src --level 5` +- PHPStan configuration is in `phpstan.dist.neon` + +### Running the Application + +- Development server: `symfony serve` (requires Symfony CLI) +- Or configure Apache/nginx to serve from `public/` directory +- Set `APP_ENV=dev` in `.env.local` for development mode + +## Best Practices + +### Security + +- Always sanitize user input +- Use Symfony's security component for authentication/authorization +- Check permissions using the permission system before allowing actions +- Never expose sensitive data in logs or error messages +- Use parameterized queries (Doctrine handles this automatically) + +### Performance + +- Use Doctrine query builder for complex queries instead of DQL when possible +- Lazy load relationships to avoid N+1 queries +- Cache results when appropriate using Symfony's cache component +- Use pagination for large result sets (DataTables integration available) + +### Database + +- Always create migrations for schema changes: `php bin/console make:migration` +- Review migration files before running them +- Use Doctrine annotations or attributes for entity mapping +- Follow existing entity patterns for relationships and lifecycle callbacks + +### Frontend + +- Use Stimulus controllers for interactive components +- Leverage Turbo for dynamic page updates without full page reloads +- Use Bootstrap 5 classes for styling +- Keep JavaScript modular and organized in controllers +- Use the translation system for user-facing strings + +### Translations + +- Use translation keys, not hardcoded strings: `{{ 'part.info.title'|trans }}` +- Add new translation keys to `translations/` files +- Primary language is English (en) +- Translations are managed via Crowdin, but can be edited locally if needed + +### Testing + +- Write unit tests for services and helpers +- Write functional tests for controllers +- Use fixtures for test data +- Tests should be isolated and not depend on execution order +- Mock external dependencies when appropriate +- Follow existing test patterns in the repository + +## Common Patterns + +### Creating an Entity + +1. Create entity class in `src/Entity/` with Doctrine attributes +2. Generate migration: `php bin/console make:migration` +3. Review and run migration: `php bin/console doctrine:migrations:migrate` +4. Create repository if needed in `src/Repository/` +5. Add fixtures in `src/DataFixtures/` for testing + +### Adding a Form + +1. Create form type in `src/Form/` +2. Extend `AbstractType` and implement `buildForm()` and `configureOptions()` +3. Use in controller and render in Twig template +4. Follow existing form patterns for consistency + +### Creating a Controller Action + +1. Add method to appropriate controller in `src/Controller/` +2. Use route attributes for routing +3. Check permissions using security voters +4. Return Response or render Twig template +5. Add corresponding template in `templates/` + +### Adding a Service + +1. Create service class in `src/Services/` +2. Use dependency injection via constructor +3. Tag service in `config/services.yaml` if needed +4. Services are autowired by default + +## Important Notes + +- Part-DB uses fine-grained permissions - always check user permissions before actions +- Multi-language support is critical - use translation keys everywhere +- The application supports multiple database backends - write portable code +- Responsive design is important - test on mobile/tablet viewports +- Event system is used for logging changes - emit events when appropriate +- API Platform is integrated for REST API endpoints + +## Multi-tenancy Considerations + +- Part-DB is designed as a single-tenant application with multiple users +- User groups have different permission levels +- Always scope queries to respect user permissions +- Use the security context to get current user information + +## Resources + +- [Documentation](https://docs.part-db.de/) +- [Contributing Guide](CONTRIBUTING.md) +- [Symfony Documentation](https://symfony.com/doc/current/index.html) +- [Doctrine Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/current/) +- [Bootstrap 5 Documentation](https://getbootstrap.com/docs/5.1/) +- [Hotwire Documentation](https://hotwired.dev/) diff --git a/.github/workflows/assets_artifact_build.yml b/.github/workflows/assets_artifact_build.yml index 0c3c1568..8ce7ccf6 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,19 +22,27 @@ jobs: APP_ENV: prod steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + ini-values: xdebug.max_nesting_level=1000 + extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr - name: Get Composer Cache Directory id: composer-cache run: | echo "::set-output name=dir::$(composer config cache-files-dir)" - - uses: actions/cache@v3 + - 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 --no-dev -a @@ -40,7 +51,7 @@ jobs: id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - - uses: actions/cache@v3 + - uses: actions/cache@v4 id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} @@ -49,9 +60,9 @@ jobs: ${{ runner.os }}-yarn- - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v6 with: - node-version: '18' + node-version: '20' - name: Install yarn dependencies run: yarn install @@ -69,13 +80,13 @@ jobs: run: zip -r /tmp/partdb_assets.zip public/build/ vendor/ - name: Upload assets artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v5 with: name: Only dependencies and built assets path: /tmp/partdb_assets.zip - name: Upload full artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v5 with: name: Full Part-DB including dependencies and built assets path: /tmp/partdb_with_assets.zip diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index 79d01893..ce3243ca 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,11 +20,11 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Docker meta id: docker_meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: # list of Docker images to use as base name for tags images: | @@ -49,23 +52,23 @@ jobs: - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 with: platforms: 'arm64,arm' - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64,linux/arm/v7 @@ -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 new file mode 100644 index 00000000..1180f0c5 --- /dev/null +++ b/.github/workflows/docker_frankenphp.yml @@ -0,0 +1,80 @@ +name: Docker Image Build (FrankenPHP) + +permissions: + contents: read + +on: + #schedule: + # - cron: '0 10 * * *' # everyday at 10am + push: + branches: + - '**' + - '!l10n_**' + tags: + - 'v*.*.*' + - 'v*.*.*-**' + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v6 + - + name: Docker meta + id: docker_meta + uses: docker/metadata-action@v5 + with: + # list of Docker images to use as base name for tags + images: | + partdborg/part-db + # Mark the image build from master as latest (as we dont have really releases yet) + tags: | + type=edge,branch=master + type=ref,event=branch, + type=ref,event=tag, + type=schedule + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=ref,event=branch + type=ref,event=pr + labels: | + org.opencontainers.image.source=${{ github.event.repository.clone_url }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.title=Part-DB + org.opencontainers.image.description=Part-DB is a web application for managing electronic components and your inventory. + org.opencontainers.image.url=https://github.com/Part-DB/Part-DB-server + org.opencontainers.image.source=https://github.com/Part-DB/Part-DB-server + org.opencontainers.image.authors=Jan Böhmer + org.opencontainers.licenses=AGPL-3.0-or-later + + - + name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: 'arm64,arm' + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - + name: Login to DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - + name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile-frankenphp + platforms: linux/amd64,linux/arm64,linux/arm/v7 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.docker_meta.outputs.tags }} + labels: ${{ steps.docker_meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 41ff1a56..d8b099eb 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,26 +19,34 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + coverage: none + ini-values: xdebug.max_nesting_level=1000 + extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr - name: Get Composer Cache Directory id: composer-cache run: | echo "::set-output name=dir::$(composer config cache-files-dir)" - - - uses: actions/cache@v3 + + - 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 @@ -45,12 +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: ./bin/console cache:clear --env dev - + run: php -d xdebug.max_nesting_level=1000 ./bin/console cache:clear --env dev + - name: Run PHPstan run: composer phpstan diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 99955059..822f5af6 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,17 +12,17 @@ on: branches: - '*' - "!l10n_*" - + jobs: phpunit: name: PHPUnit and coverage Test (PHP ${{ matrix.php-versions }}, ${{ matrix.db-type }}) - # Ubuntu 20.04 ships MySQL 8.0 which causes problems with login, so we just use ubuntu 18.04 for now... runs-on: ubuntu-22.04 strategy: + fail-fast: false matrix: - php-versions: [ '8.1', '8.2' ] - db-type: [ 'mysql', 'sqlite' ] + php-versions: ['8.2', '8.3', '8.4', '8.5' ] + db-type: [ 'mysql', 'sqlite', 'postgres' ] env: # Note that we set DATABASE URL later based on our db-type matrix value @@ -27,115 +30,128 @@ jobs: SYMFONY_DEPRECATIONS_HELPER: disabled PHP_VERSION: ${{ matrix.php-versions }} DB_TYPE: ${{ matrix.db-type }} + CHECK_FOR_UPDATES: false # Disable update checks for tests steps: - name: Set Database env for MySQL - run: echo "DATABASE_URL=mysql://root:root@127.0.0.1:3306/test" >> $GITHUB_ENV + run: echo "DATABASE_URL=mysql://root:root@127.0.0.1:3306/partdb?serverVersion=8.0.35" >> $GITHUB_ENV if: matrix.db-type == 'mysql' - name: Set Database env for SQLite run: echo "DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db"" >> $GITHUB_ENV if: matrix.db-type == 'sqlite' + - name: Set Database env for PostgreSQL + run: echo "DATABASE_URL=postgresql://postgres:postgres @127.0.0.1:5432/partdb?serverVersion=14&charset=utf8" >> $GITHUB_ENV + if: matrix.db-type == 'postgres' + - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} coverage: pcov - extensions: mbstring, intl, gd, xsl, gmp, bcmath - + 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' - #- name: Setup MySQL + # Replace the scram-sha-256 with trust for host connections, to avoid password authentication + - name: Start PostgreSQL + run: | + sudo sed -i 's/^\(host.*all.*all.*\)scram-sha-256/\1trust/' /etc/postgresql/14/main/pg_hba.conf + sudo systemctl start postgresql.service + sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'postgres';" + if: matrix.db-type == 'postgres' + + #- name: Setup MySQL # uses: mirromutth/mysql-action@v1.1 # with: # mysql version: 5.7 # mysql database: 'part-db' # mysql root password: '1234' - + ## Setup caches - + - name: Get Composer Cache Directory id: composer-cache run: | echo "::set-output name=dir::$(composer config cache-files-dir)" - - uses: actions/cache@v3 + - 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: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - - uses: actions/cache@v3 + - uses: actions/cache@v4 id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 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@v3 + uses: actions/setup-node@v6 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' + if: matrix.db-type == 'mysql' || matrix.db-type == 'postgres' - # Checkinf for existance is not supported for sqlite, so do it without it - - name: Create DB - run: php bin/console --env test doctrine:database:create -n - if: matrix.db-type == 'sqlite' - - 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 doctrine:fixtures:load -n - + 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@v3 + 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 - name: Test check-requirements command run: php bin/console partdb:check-requirements -n + - name: Test partdb:backup command + run: php bin/console partdb:backup -n --full /tmp/test_backup.zip + - name: Test legacy Part-DB import run: bash .github/assets/legacy_import/test_legacy_import.sh if: matrix.db-type == 'mysql' && matrix.php-versions == '8.2' env: DATABASE_URL: mysql://root:root@localhost:3306/legacy_db - - - diff --git a/.gitignore b/.gitignore index 1d28a771..dd5c43db 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,5 +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/CONTRIBUTING.md b/CONTRIBUTING.md index d31c904e..5994a115 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ # How to contribute -Thank you for consider to contribute to Part-DB! -Please read the text below, so your contributed content can be contributed easily to Part-DB. +Thank you for considering contributing to Part-DB! +Please read the text below, so your contributed content can be incorporated into Part-DB easily. You can contribute to Part-DB in various ways: * Report bugs and request new features via [issues](https://github.com/Part-DB/Part-DB-server/issues) @@ -18,38 +18,38 @@ Part-DB uses translation keys (e.g. part.info.title) that are sorted by their us was translated in other languages (this is possible via the "Other languages" dropdown in the translation editor). ## Project structure -Part-DB uses symfony's recommended [project structure](https://symfony.com/doc/current/best_practices.html). +Part-DB uses Symfony's recommended [project structure](https://symfony.com/doc/current/best_practices.html). Interesting folders are: -* `public`: Everything in this directory will be publicy accessible via web. Use this folder to serve static images. -* `assets`: The frontend assets are saved here. You can find the javascript and CSS code here. -* `src`: Part-DB's PHP code is saved here. Note that the sub directories are structured by the classes purposes (so use `Controller` Controllers, `Entities` for Database models, etc.) -* `translations`: The translations used in Part-DB are saved here +* `public`: Everything in this directory will be publicly accessible via web. Use this folder to serve static images. +* `assets`: The frontend assets are saved here. You can find the JavaScript and CSS code here. +* `src`: Part-DB's PHP code is saved here. Note that the subdirectories are structured by the classes' purposes (so use `Controller` for Controllers, `Entity` for Database models, etc.) +* `translations`: The translations used in Part-DB are saved here. * `templates`: The templates (HTML) that are used by Twig to render the different pages. Email templates are also saved here. -* `tests/`: Tests that can be run by PHPunit. +* `tests/`: Tests that can be run by PHPUnit. ## Development environment -For setting up an development you will need to install PHP, composer, a database server (MySQL or MariaDB) and yarn (which needs an nodejs environment). -* Copy `.env` to `.env.local` and change `APP_ENV` to `APP_ENV=dev`. That way you will get development tools (symfony profiler) and other features that +For setting up a development environment, you will need to install PHP, Composer, a database server (MySQL or MariaDB) and yarn (which needs a Node.js environment). +* Copy `.env` to `.env.local` and change `APP_ENV` to `APP_ENV=dev`. That way you will get development tools (Symfony profiler) and other features that will simplify development. -* Run `composer install` (without -o) to install PHP dependencies and `yarn install` to install frontend dependencies -* Run `yarn watch`. The program will run in the background and compile the frontend files whenever you change something in the CSS or TypeScript files -* For running Part-DB it is recommended to use [Symfony CLI](https://symfony.com/download). -That way you can run a correct configured webserver with `symfony serve` +* Run `composer install` (without -o) to install PHP dependencies and `yarn install` to install frontend dependencies. +* Run `yarn watch`. The program will run in the background and compile the frontend files whenever you change something in the CSS or TypeScript files. +* For running Part-DB, it is recommended to use [Symfony CLI](https://symfony.com/download). +That way you can run a correctly configured webserver with `symfony serve`. ## Coding style -Code should follow the [PSR12-Standard](https://www.php-fig.org/psr/psr-12/) and symfony's [coding standards](https://symfony.com/doc/current/contributing/code/standards.html). +Code should follow the [PSR-12 Standard](https://www.php-fig.org/psr/psr-12/) and Symfony's [coding standards](https://symfony.com/doc/current/contributing/code/standards.html). Part-DB uses [Easy Coding Standard](https://github.com/symplify/easy-coding-standard) to check and fix coding style violations: -* To check your code for valid code style run `vendor/bin/ecs check src/` -* To fix violations run `vendor/bin/ecs check src/` (please checks afterwards if the code is valid afterwards) +* To check your code for valid code style, run `vendor/bin/ecs check src/` +* To fix violations, run `vendor/bin/ecs check src/ --fix` (please check afterwards if the code is still valid) ## GitHub actions -Part-DB uses GitHub actions to run various tests and checks on the code: +Part-DB uses GitHub Actions to run various tests and checks on the code: * Yarn dependencies can compile -* PHPunit tests run successful -* Config files, translations and templates has valid syntax -* Doctrine schema valid -* No known vulnerable dependecies are used -* Static analysis successful (phpstan with `--level=2`) +* PHPUnit tests run successfully +* Config files, translations, and templates have valid syntax +* Doctrine schema is valid +* No known vulnerable dependencies are used +* Static analysis is successful (phpstan with `--level=2`) -Further the code coverage of the PHPunit tests is determined and uploaded to [CodeCov](https://codecov.io/gh/Part-DB/Part-DB-server). +Further, the code coverage of the PHPUnit tests is determined and uploaded to [CodeCov](https://codecov.io/gh/Part-DB/Part-DB-server). diff --git a/Dockerfile b/Dockerfile index 4a9048ba..cb18c78f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,64 @@ -FROM debian:bullseye-slim +ARG BASE_IMAGE=debian:bookworm-slim +ARG PHP_VERSION=8.4 + +FROM ${BASE_IMAGE} AS base +ARG PHP_VERSION # Install needed dependencies for PHP build #RUN apt-get update && apt-get install -y pkg-config curl libcurl4-openssl-dev libicu-dev \ # libpng-dev libjpeg-dev libfreetype6-dev gnupg zip libzip-dev libjpeg62-turbo-dev libonig-dev libxslt-dev libwebp-dev vim \ # && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/* -RUN apt-get update && apt-get -y install apt-transport-https lsb-release ca-certificates curl zip \ +RUN apt-get update && apt-get -y install \ + apt-transport-https \ + lsb-release \ + ca-certificates \ + curl \ + zip \ + mariadb-client \ + postgresql-client \ && curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg \ && sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' \ && apt-get update && apt-get upgrade -y \ - && apt-get install -y apache2 php8.1 php8.1-fpm php8.1-opcache php8.1-curl php8.1-gd php8.1-mbstring php8.1-xml php8.1-bcmath php8.1-intl php8.1-zip php8.1-xsl php8.1-sqlite3 php8.1-mysql gpg \ - && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*; - -ENV APACHE_CONFDIR /etc/apache2 -ENV APACHE_ENVVARS $APACHE_CONFDIR/envvars - + && apt-get install -y \ + apache2 \ + php${PHP_VERSION} \ + php${PHP_VERSION}-fpm \ + php${PHP_VERSION}-opcache \ + php${PHP_VERSION}-curl \ + php${PHP_VERSION}-gd \ + php${PHP_VERSION}-mbstring \ + php${PHP_VERSION}-xml \ + php${PHP_VERSION}-bcmath \ + php${PHP_VERSION}-intl \ + php${PHP_VERSION}-zip \ + php${PHP_VERSION}-xsl \ + php${PHP_VERSION}-sqlite3 \ + php${PHP_VERSION}-mysql \ + php${PHP_VERSION}-pgsql \ + gpg \ + sudo \ + && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/* \ # Create workdir and set permissions if directory does not exists -RUN mkdir -p /var/www/html && chown -R www-data:www-data /var/www/html + && mkdir -p /var/www/html \ + && chown -R www-data:www-data /var/www/html \ +# delete the "index.html" that installing Apache drops in here + && rm -rvf /var/www/html/* + +# Install node and yarn +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ + echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ + curl -sL https://deb.nodesource.com/setup_22.x | bash - && \ + apt-get update && apt-get install -y \ + nodejs \ + yarn \ + && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/* + +# Install composer +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +ENV APACHE_CONFDIR=/etc/apache2 +ENV APACHE_ENVVARS=$APACHE_CONFDIR/envvars # Configure apache 2 (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/Dockerfile) # generically convert lines like @@ -27,8 +69,6 @@ RUN mkdir -p /var/www/html && chown -R www-data:www-data /var/www/html # so that they can be overridden at runtime ("-e APACHE_RUN_USER=...") RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"; \ set -eux; . "$APACHE_ENVVARS"; \ - # delete the "index.html" that installing Apache drops in here - rm -rvf /var/www/html/*; \ \ # logs should go to stdout / stderr ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log"; \ @@ -36,82 +76,87 @@ RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS" ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"; \ chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR"; -# Enable php-fpm -RUN a2enmod proxy_fcgi setenvif && a2enconf php8.1-fpm +# --- +FROM scratch AS apache-config +ARG PHP_VERSION # Configure php-fpm to log to stdout of the container (stdout of PID 1) # We have to use /proc/1/fd/1 because /dev/stdout or /proc/self/fd/1 does not point to the container stdout (because we use apache as entrypoint) # We also disable the clear_env option to allow the use of environment variables in php-fpm -RUN { \ - echo '[global]'; \ - echo 'error_log = /proc/1/fd/1'; \ - echo; \ - echo '[www]'; \ - echo 'access.log = /proc/1/fd/1'; \ - echo 'catch_workers_output = yes'; \ - echo 'decorate_workers_output = no'; \ - echo 'clear_env = no'; \ - } | tee "/etc/php/8.1/fpm/pool.d/zz-docker.conf" +COPY <'; \ - echo '\tSetHandler application/x-httpd-php'; \ - echo ''; \ - echo; \ - echo 'DirectoryIndex disabled'; \ - echo 'DirectoryIndex index.php index.html'; \ - echo; \ - echo ''; \ - echo '\tOptions -Indexes'; \ - echo '\tAllowOverride All'; \ - echo ''; \ - } | tee "$APACHE_CONFDIR/conf-available/docker-php.conf" \ - && a2enconf docker-php +COPY < + SetHandler application/x-httpd-php + + +DirectoryIndex disabled +DirectoryIndex index.php index.html + + + Options -Indexes + AllowOverride All + +EOF # Enable opcache and configure it recommended for symfony (see https://symfony.com/doc/current/performance.html) -RUN \ - { \ - echo 'opcache.memory_consumption=256'; \ - echo 'opcache.max_accelerated_files=20000'; \ - echo 'opcache.validate_timestamp=0'; \ - # Configure Realpath cache for performance - echo 'realpath_cache_size=4096K'; \ - echo 'realpath_cache_ttl=600'; \ - } > /etc/php/8.1/fpm/conf.d/symfony-recommended.ini +COPY < /etc/php/8.1/fpm/conf.d/partdb.ini +# 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; \ + install-php-extensions \ + @composer \ + apcu \ + intl \ + opcache \ + zip \ + pdo_mysql \ + pdo_sqlite \ + pdo_pgsql \ + gd \ + bcmath \ + xsl \ + ; + +# Copy config files for php and caddy +COPY --link .docker/frankenphp/conf.d/app.ini $PHP_INI_DIR/conf.d/ +COPY --chmod=755 .docker/frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint +COPY --link .docker/frankenphp/Caddyfile /etc/caddy/Caddyfile +COPY --link .docker/frankenphp/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/ +COPY --link .docker/frankenphp/worker.Caddyfile /etc/caddy/worker.Caddyfile +ENV FRANKENPHP_CONFIG="import worker.Caddyfile" + +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +# Install composer +ENV COMPOSER_ALLOW_SUPERUSER=1 +#COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +# Create workdir and set permissions if directory does not exists +WORKDIR /app + +# prevent the reinstallation of vendors at every changes in the source code +COPY --link composer.* symfony.* ./ +RUN set -eux; \ + composer install --no-cache --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress + +# copy sources +COPY --link . ./ + +# Install composer and yarn dependencies for Part-DB +RUN set -eux; \ + mkdir -p var/cache var/log; \ + composer dump-autoload --classmap-authoritative --no-dev; \ + composer dump-env prod; \ + composer run-script --no-dev post-install-cmd; \ + chmod +x bin/console; sync; + +RUN yarn install --network-timeout 600000 && \ + yarn build && \ + yarn cache clean && \ + rm -rf node_modules/ + +# Use docker env to output logs to stdout +ENV APP_ENV=docker +ENV DATABASE_URL="sqlite:///%kernel.project_dir%/uploads/app.db" + +USER root + +ENTRYPOINT ["docker-entrypoint"] +CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"] + +# https://httpd.apache.org/docs/2.4/stopping.html#gracefulstop +STOPSIGNAL SIGWINCH + +VOLUME ["/var/www/html/uploads", "/var/www/html/public/media"] + +HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1 + +# See https://caddyserver.com/docs/conventions#file-locations for details +ENV XDG_CONFIG_HOME /config +ENV XDG_DATA_HOME /data + +EXPOSE 80 +EXPOSE 443 +EXPOSE 443/udp +EXPOSE 2019 diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..bc4d0bf3 --- /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 de4b7bc4..993a1a9c 100644 --- a/README.md +++ b/README.md @@ -1,128 +1,164 @@ [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Part-DB/Part-DB-symfony/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Part-DB/Part-DB-symfony/?branch=master) ![PHPUnit Tests](https://github.com/Part-DB/Part-DB-symfony/workflows/PHPUnit%20Tests/badge.svg) ![Static analysis](https://github.com/Part-DB/Part-DB-symfony/workflows/Static%20analysis/badge.svg) -[![codecov](https://codecov.io/gh/Part-DB/Part-DB-symfony/branch/master/graph/badge.svg)](https://codecov.io/gh/Part-DB/Part-DB-server) +[![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) [![Crowdin](https://badges.crowdin.net/e/8325196085d4bee8c04b75f7c915452a/localized.svg)](https://part-db.crowdin.com/part-db) -**[Documentation](https://docs.part-db.de/)** | **[Demo](https://part-db.herokuapp.com)** | **[Docker Image](https://hub.docker.com/r/jbtronics/part-db1)** +**[Documentation](https://docs.part-db.de/)** | **[Demo](https://demo.part-db.de/)** | **[Docker Image](https://hub.docker.com/r/jbtronics/part-db1)** # Part-DB -Part-DB is an Open-Source inventory managment system for your electronic components. + +Part-DB is an Open-Source inventory management system for your electronic components. It is installed on a web server and so can be accessed with any browser without the need to install additional software. -The version in this Repository is a complete rewrite of the legacy [Part-DB](https://github.com/Part-DB/Part-DB) (Version < 1.0) based on a modern framework. -Currently, it is still missing some (minor) features from the old version (see [UPGRADE.md](https://docs.part-db.de/upgrade_legacy.html)) for more details, but also many huge improvements and advantages compared to the old version. -If you start completely new with Part-DB it is recommended that you use the version from this repository, as it is actively developed. +The version in this repository is a complete rewrite of the legacy [Part-DB](https://github.com/Part-DB/Part-DB) +(Version < 1.0) based on a modern framework and is the recommended version to use. -If you find a bug, please open an [Issue on Github](https://github.com/Part-DB/Part-DB-server/issues) so it can be fixed for everybody. +If you find a bug, please open an [Issue on GitHub,](https://github.com/Part-DB/Part-DB-server/issues) so it can be fixed +for everybody. ## Demo -If you want to test Part-DB without installing it, you can use [this](https://part-db.herokuapp.com) Heroku instance. -(Or this link for the [German Version](https://part-db.herokuapp.com/de/)). + +If you want to test Part-DB without installing it, you can use [this](https://demo.part-db.de/) Heroku instance. +(Or this link for the [German Version](https://demo.part-db.de/de/)). You can log in with username: *user* and password: *user*. -Every change to the master branch gets automatically deployed, so it represents the current development progress and is -maybe not completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading the page +Every change to the master branch gets automatically deployed, so it represents the current development progress and +may not be completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading +the page for the first time. ## Features -* Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer -and multiple store locations and price information. Parts can be grouped using tags. You can associate various files like datasheets or pictures with the parts. -* Multi-Language support (currently German, English, Russian, Japanese and French (experimental)) + +* Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer, + and multiple store locations and price information. Parts can be grouped using tags. You can associate various files + like datasheets or pictures with the parts. +* Multi-language support (currently German, English, Russian, Japanese, French, Czech, Danish, and Chinese) * Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the builtin barcode scanner -* User system with groups and detailed (fine granular) permissions. -Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups. Password reset via email can be setuped. -* Optional support for single sign-on (SSO) via SAML (using an intermediate service like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server) -* Import/Export system for parts and datastructure. BOM import for projects from KiCAD is supported. -* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build this project and directly withdraw all components needed from DB -* Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older versions. -* Responsive design: You can use Part-DB on your PC, your tablet and your smartphone using the same interface. -* MySQL and SQLite supported as database backends +* User system with groups and detailed (fine granular) permissions. + Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups. + Password reset via email can be set up. +* Optional support for single sign-on (SSO) via SAML (using an intermediate service + like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server) +* Import/Export system for parts and data structure. BOM import for projects from KiCAD is supported. +* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build + this project and directly withdraw all components needed from DB +* Event log: Track what changes happen to your inventory, track which user does what. Revert your parts to older + versions. +* Responsive design: You can use Part-DB on your PC, your tablet, and your smartphone using the same interface. +* MySQL, SQLite and PostgreSQL are supported as database backends * Support for rich text descriptions and comments in parts * Support for multiple currencies and automatic update of exchange rates supported * Powerful search and filter function, including parametric search (search for parts according to some specifications) * Automatic thumbnail generation for pictures -* Use cloud providers (like Octopart, Digikey, farnell or TME) to automatically get part information, datasheets and prices for parts +* Use cloud providers (like Octopart, Digikey, Farnell, LCSC or TME) to automatically get part information, datasheets, and + prices for parts +* API to access Part-DB from other applications/scripts +* [Integration with KiCad](https://docs.part-db.de/usage/eda_integration.html): Use Part-DB as the central datasource for your + KiCad and see available parts from Part-DB directly inside KiCad. - -With these features Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory, -or makerspaces, where many users have should have (controlled) access to the shared inventory. +With these features, Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory, +or maker spaces, where many users should have (controlled) access to the shared inventory. Part-DB is also used by small companies and universities for managing their inventory. ## Requirements - * A **web server** (like Apache2 or nginx) that is capable of running [Symfony 5](https://symfony.com/doc/current/reference/requirements.html), - this includes a minimum PHP version of **PHP 8.1** - * A **MySQL** (at least 5.7) /**MariaDB** (at least 10.2.2) database server if you do not want to use SQLite. - * Shell access to your server is highly suggested! - * For building the client side assets **yarn** and **nodejs** (>= 18.0) is needed. - + +* 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.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** (>= 20.0) is needed. + ## Installation -If you want to upgrade your legacy (< 1.0.0) version of Part-DB to this version, please read [this](https://docs.part-db.de/upgrade_legacy.html) first. -*Hint:* A docker image is available under [jbtronics/part-db1](https://hub.docker.com/r/jbtronics/part-db1). How to set up Part-DB via docker is described [here](https://docs.part-db.de/installation/installation_docker.html). +If you want to upgrade your legacy (< 1.0.0) version of Part-DB to this version, please +read [this](https://docs.part-db.de/upgrade_legacy.html) first. -**Below you find some very rough outline of the installation process, see [here](https://docs.part-db.de/installation/) for a detailed guide how to install Part-DB.** +*Hint:* A docker image is available under [jbtronics/part-db1](https://hub.docker.com/r/jbtronics/part-db1). How to set +up Part-DB via docker is described [here](https://docs.part-db.de/installation/installation_docker.html). + +**Below you find a very rough outline of the installation process, see [here](https://docs.part-db.de/installation/) +for a detailed guide on how to install Part-DB.** 1. Copy or clone this repository into a folder on your server. -2. Configure your webserver to serve from the `public/` folder. See [here](https://symfony.com/doc/current/setup/web_server_configuration.html) -for additional information. +2. Configure your webserver to serve from the `public/` folder. + See [here](https://symfony.com/doc/current/setup/web_server_configuration.html) + for additional information. 3. Copy the global config file `cp .env .env.local` and edit `.env.local`: * Change the line `APP_ENV=dev` to `APP_ENV=prod` - * If you do not want to use SQLite, change the value of `DATABASE_URL=` to your needs (see [here](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url)) for the format. - In bigger instances with concurrent accesses, MySQL is more performant. This can not be changed easily later, so choose wisely. + * If you do not want to use SQLite, change the value of `DATABASE_URL=` to your needs ( + see [here](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url)) + for the format. + In bigger instances with concurrent accesses, MySQL is more performant. This can not be changed easily later, so + choose wisely. 4. Install composer dependencies and generate autoload files: `composer install -o --no-dev` -5. If you have put Part-DB into a sub-directory on your server (like `part-db/`), you have to edit the file -`webpack.config.js` and uncomment the lines (remove the `//` before the lines) `.setPublicPath('/part-db/build')` (line 43) and - `.setManifestKeyPrefix('build/')` (line 44). You have to replace `/part-db` with your own path on line 44. -6. Install client side dependencies and build it: `yarn install` and `yarn build` -7. _Optional_ (speeds up first load): Warmup cache: `php bin/console cache:warmup` -8. Upgrade database to new scheme (or create it, when it was empty): `php bin/console doctrine:migrations:migrate` and follow the instructions given. During the process the password for the admin is user is shown. Copy it. **Caution**: This steps tamper with your database and could potentially destroy it. So make sure to make a backup of your database. -9. You can configure Part-DB via `config/parameters.yaml`. You should check if settings match your expectations, after you installed/upgraded Part-DB. Check if `partdb.default_currency` matches your mainly used currency (this can not be changed after creating price informations). - Run `php bin/console cache:clear` when you changed something. -10. Access Part-DB in your browser (under the URL you put it) and login with user *admin*. Password is the one outputted during DB setup. - If you can not remember the password, set a new one with `php bin/console app:set-password admin`. You can create new users with the admin user and start using Part-DB. +5. Install client side dependencies and build it: `yarn install` and `yarn build` +6. _Optional_ (speeds up first load): Warmup cache: `php bin/console cache:warmup` +7. Upgrade database to new scheme (or create it, when it was empty): `php bin/console doctrine:migrations:migrate` and + follow the instructions given. During the process the password for the admin is user is shown. Copy it. **Caution**: + These steps tamper with your database and could potentially destroy it. So make sure to make a backup of your + database. +8. You can configure Part-DB via `config/parameters.yaml`. You should check if settings match your expectations after + you installed/upgraded Part-DB. Check if `partdb.default_currency` matches your mainly used currency (this can not be + changed after creating price information). + Run `php bin/console cache:clear` when you change something. +9. Access Part-DB in your browser (under the URL you put it) and log in with user *admin*. Password is the one outputted + during DB setup. + If you can not remember the password, set a new one with `php bin/console app:set-password admin`. You can create + new users with the admin user and start using Part-DB. When you want to upgrade to a newer version, then just copy the new files into the folder and repeat the steps 4. to 7. -Normally a random password is generated when the admin user is created during inital database creation, -however you can set the inital admin password, by setting the `INITIAL_ADMIN_PW` env var. +Normally a random password is generated when the admin user is created during initial database creation, +however, you can set the initial admin password, by setting the `INITIAL_ADMIN_PW` env var. -You can configure Part-DB to your needs by changing environment variables in the `.env.local` file. +You can configure Part-DB to your needs by changing environment variables in the `.env.local` file. See [here](https://docs.part-db.de/configuration.html) for more information. ### Reverse proxy -If you are using a reverse proxy, you have to ensure that the proxies sets the `X-Forwarded-*` headers correctly, or you will get HTTP/HTTPS mixup and wrong hostnames. -If the reverse proxy is on a different server (or it cannot access Part-DB via localhost) you have to set the `TRUSTED_PROXIES` env variable to match your reverse proxies IP-address (or IP block). You can do this in your `.env.local` or (when using docker) in your `docker-compose.yml` file. + +If you are using a reverse proxy, you have to ensure that the proxies set the `X-Forwarded-*` headers correctly, or you +will get HTTP/HTTPS mixup and wrong hostnames. +If the reverse proxy is on a different server (or it cannot access Part-DB via localhost) you have to set +the `TRUSTED_PROXIES` env variable to match your reverse proxy's IP address (or IP block). You can do this in +your `.env.local` or (when using docker) in your `docker-compose.yml` file. ## Donate for development + If you want to donate to the Part-DB developer, see the sponsor button in the top bar (next to the repo name). -There you will find various methods to support development on a monthly or a one time base. +There you will find various methods to support development on a monthly or a one-time base. ## Built with -* [Symfony 5](https://symfony.com/): The main framework used for the serverside PHP + +* [Symfony 6](https://symfony.com/): The main framework used for the serverside PHP * [Bootstrap 5](https://getbootstrap.com/) and [Bootswatch](https://bootswatch.com/): Used as website theme * [Fontawesome](https://fontawesome.com/): Used as icon set -* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend Javascript +* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend + Javascript ## Authors -* **Jan Böhmer** - *Inital work* - [Github](https://github.com/jbtronics/) -See also the list of [contributors](https://github.com/Part-DB/Part-DB-server/graphs/contributors) who participated in this project. +* **Jan Böhmer** - *Initial work* - [GitHub](https://github.com/jbtronics/) + +See also the list of [contributors](https://github.com/Part-DB/Part-DB-server/graphs/contributors) who participated in +this project. Based on the original Part-DB by Christoph Lechner and K. Jacobs ## License + Part-DB is licensed under the GNU Affero General Public License v3.0 (or at your opinion any later). This mostly means that you can use Part-DB for whatever you want (even use it commercially) as long as you publish the source code for every change you make under the AGPL, too. diff --git a/VERSION b/VERSION index bd8bf882..276cbf9e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.7.0 +2.3.0 diff --git a/assets/ckeditor/emojis.json b/assets/ckeditor/emojis.json new file mode 100644 index 00000000..a6bafe26 --- /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 9040f3c7..72d1126e 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 784bd688..76944b86 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 f7e91aa9..f05983a2 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 01e1c7bf..708d4ebb 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 4c3af3ef..7b9797e7 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 e61fb895..5cb4860f 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 03737dae..bb9fcd1f 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() { @@ -85,6 +86,9 @@ const PLACEHOLDERS = [ ['[[COMMENT_T]]', 'Comment (plain text)'], ['[[LAST_MODIFIED]]', 'Last modified datetime'], ['[[CREATION_DATE]]', 'Creation datetime'], + ['[[IPN_BARCODE_QR]]', 'IPN as QR code'], + ['[[IPN_BARCODE_C128]]', 'IPN as Code 128 barcode'], + ['[[IPN_BARCODE_C39]]', 'IPN as Code 39 barcode'], ] }, { @@ -125,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'], ] }, { @@ -146,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, @@ -168,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], @@ -182,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 53007a0a..e0ca0521 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', @@ -48,6 +42,9 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, { 'Comment (plain text)': 'Kommentar (Nur-Text)', 'Last modified datetime': 'Zuletzt geändert', 'Creation datetime': 'Erstellt', + 'IPN as QR code': 'IPN als QR Code', + 'IPN as Code 128 barcode': 'IPN als Code 128 Barcode', + 'IPN as Code 39 barcode': 'IPN als Code 39 Barcode', 'Lot ID': 'Lot ID', 'Lot name': 'Lot Name', @@ -66,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', @@ -83,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 00000000..8f77aaf1 --- /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 987388cd..fc861175 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 21d4074c..695e7089 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 79ac9c6f..be84dd22 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 1d4ec000..1a57def2 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 00000000..49e4d60f --- /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 00000000..c26e37c6 --- /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/hide_sidebar_controller.js b/assets/controllers/common/hide_sidebar_controller.js index d4c1b5e2..4be304ff 100644 --- a/assets/controllers/common/hide_sidebar_controller.js +++ b/assets/controllers/common/hide_sidebar_controller.js @@ -88,5 +88,8 @@ export default class extends Controller { } else { this.hideSidebar(); } + + //Hide the tootip on the button + this._toggle_button.blur(); } } \ No newline at end of file diff --git a/assets/controllers/common/markdown_controller.js b/assets/controllers/common/markdown_controller.js index 91aaef66..c6cb97df 100644 --- a/assets/controllers/common/markdown_controller.js +++ b/assets/controllers/common/markdown_controller.js @@ -20,18 +20,26 @@ 'use strict'; import { Controller } from '@hotwired/stimulus'; -import { marked } from "marked"; +import { Marked } from "marked"; import { mangle } from "marked-mangle"; import { gfmHeadingId } from "marked-gfm-heading-id"; import DOMPurify from 'dompurify'; import "../../css/app/markdown.css"; -export default class extends Controller { +export default class MarkdownController extends Controller { + + static _marked = new Marked([ + { + gfm: true, + }, + gfmHeadingId(), + mangle(), + ]) + ; connect() { - this.configureMarked(); this.render(); //Dispatch an event that we are now finished @@ -45,15 +53,19 @@ export default class extends Controller { let raw = this.element.dataset['markdown']; //Apply purified parsed markdown - this.element.innerHTML = DOMPurify.sanitize(marked(this.unescapeHTML(raw))); + 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 @@ -81,8 +93,17 @@ export default class extends Controller { /** * Configure the marked parser */ - configureMarked() + /*static newMarked() { + const marked = new Marked([ + { + gfm: true, + }, + gfmHeadingId(), + mangle(), + ]) + ; + marked.use(mangle()); marked.use(gfmHeadingId({ })); @@ -90,5 +111,5 @@ export default class extends Controller { marked.setOptions({ 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 00000000..511fffa5 --- /dev/null +++ b/assets/controllers/csrf_protection_controller.js @@ -0,0 +1,81 @@ +const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/; +const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/; + +// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager +// Use `form.requestSubmit()` to ensure that the submit event is triggered. Using `form.submit()` will not trigger the event +// and thus this event-listener will not be executed. +document.addEventListener('submit', function (event) { + generateCsrfToken(event.target); +}, true); + +// 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 fe44baee..94b01136 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 4de536fe..b7c87dab 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,8 +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(); @@ -70,7 +106,18 @@ export default class extends Controller { editor_div.classList.add(...new_classes.split(",")); } - console.log(editor); + // Automatic synchronization of source input + editor.model.document.on("change:data", () => { + editor.updateSourceElement(); + + // Dispatch the input event for further treatment + const event = new Event("input"); + this.element.dispatchEvent(event); + }); + + //This return is important! Otherwise we get mysterious errors in the console + //See: https://github.com/ckeditor/ckeditor5/issues/5897#issuecomment-628471302 + return editor; }) .catch(error => { console.error(error); @@ -81,4 +128,4 @@ export default class extends Controller { console.error(error); }); } -} \ No newline at end of file +} diff --git a/assets/controllers/elements/collection_type_controller.js b/assets/controllers/elements/collection_type_controller.js index 0b961cb0..8b816f30 100644 --- a/assets/controllers/elements/collection_type_controller.js +++ b/assets/controllers/elements/collection_type_controller.js @@ -75,13 +75,49 @@ export default class extends Controller { //Insert new html after the last child element //If the table has a tbody, insert it there + //Afterwards return the newly created row if(targetTable.tBodies[0]) { targetTable.tBodies[0].insertAdjacentHTML('beforeend', newElementStr); + return targetTable.tBodies[0].lastElementChild; } else { //Otherwise just insert it targetTable.insertAdjacentHTML('beforeend', newElementStr); + return targetTable.lastElementChild; } } + /** + * This action opens a file dialog to select multiple files and then creates a new element for each file, where + * the file is assigned to the input field. + * This should only be used for attachments collection types + * @param event + */ + uploadMultipleFiles(event) { + //Open a file dialog to select multiple files + const input = document.createElement('input'); + input.type = 'file'; + input.multiple = true; + input.click(); + + input.addEventListener('change', (event) => { + //Create a element for each file + + for (let i = 0; i < input.files.length; i++) { + const file = input.files[i]; + + const newElement = this.createElement(event); + const rowInput = newElement.querySelector("input[type='file']"); + + //We can not directly assign the file to the input, so we have to create a new DataTransfer object + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(file); + + rowInput.files = dataTransfer.files; + } + + }); + + } + /** * Similar to createEvent Pricedetails need some special handling to fill min amount * @param event diff --git a/assets/controllers/elements/datatables/datatables_controller.js b/assets/controllers/elements/datatables/datatables_controller.js index c53db751..5a50623d 100644 --- a/assets/controllers/elements/datatables/datatables_controller.js +++ b/assets/controllers/elements/datatables/datatables_controller.js @@ -24,18 +24,25 @@ import 'datatables.net-bs5/css/dataTables.bootstrap5.css' import 'datatables.net-buttons-bs5/css/buttons.bootstrap5.css' import 'datatables.net-fixedheader-bs5/css/fixedHeader.bootstrap5.css' import 'datatables.net-responsive-bs5/css/responsive.bootstrap5.css'; -import 'datatables.net-select-bs5/css/select.bootstrap5.css'; + +//Use our own styles for the select extension which fit the bootstrap theme better +//import 'datatables.net-select-bs5/css/select.bootstrap5.css'; +import '../../../css/components/datatables_select_bs5.css'; //JS import 'datatables.net-bs5'; import 'datatables.net-buttons-bs5'; import 'datatables.net-buttons/js/buttons.colVis.js'; import 'datatables.net-fixedheader-bs5'; -import 'datatables.net-select-bs5'; import 'datatables.net-colreorder-bs5'; import 'datatables.net-responsive-bs5'; import '../../../js/lib/datatables'; +//import 'datatables.net-select-bs5'; +//Use the local version containing the fix for the select extension +import '../../../js/lib/dataTables.select.mjs'; + + const EVENT_DT_LOADED = 'dt:loaded'; export default class extends Controller { @@ -132,7 +139,7 @@ export default class extends Controller { if(this.isSelectable()) { options.select = { style: 'multi+shift', - selector: 'td.select-checkbox' + selector: 'td.dt-select', }; } @@ -145,6 +152,12 @@ export default class extends Controller { //Fix height of the length selector promise.then((dt) => { + + //Draw the rows to make sure the correct status text is displayed ("No matching records found" instead of "Loading...") + if (dt.data().length === 0) { + dt.rows().draw() + } + //Find all length selectors (select with name dt_length), which are inside a label const lengthSelectors = document.querySelectorAll('label select[name="dt_length"]'); //And remove the surrounding label, while keeping the select with all event handlers @@ -180,27 +193,6 @@ export default class extends Controller { dt.fixedHeader.headerOffset($("#navbar").outerHeight()); }); - //Register event handler to selectAllRows checkbox if available - if (this.isSelectable()) { - promise.then((dt) => { - const selectAllCheckbox = this.element.querySelector('thead th.select-checkbox'); - selectAllCheckbox.addEventListener('click', () => { - if(selectAllCheckbox.parentElement.classList.contains('selected')) { - dt.rows().deselect(); - selectAllCheckbox.parentElement.classList.remove('selected'); - } else { - dt.rows().select(); - selectAllCheckbox.parentElement.classList.add('selected'); - } - }); - - //When any row is deselected, also deselect the selectAll checkbox - dt.on('deselect.dt', () => { - selectAllCheckbox.parentElement.classList.remove('selected'); - }); - }); - } - //Allow to further configure the datatable promise.then(this._afterLoaded.bind(this)); diff --git a/assets/controllers/elements/datatables/parts_controller.js b/assets/controllers/elements/datatables/parts_controller.js index 1fe11a20..c43fa276 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/delete_btn_controller.js b/assets/controllers/elements/delete_btn_controller.js index 1b28de13..9ab15f7d 100644 --- a/assets/controllers/elements/delete_btn_controller.js +++ b/assets/controllers/elements/delete_btn_controller.js @@ -43,7 +43,8 @@ export default class extends Controller const message = this.element.dataset.deleteMessage; const title = this.element.dataset.deleteTitle; - const form = this.element; + //Use event target, to find the form, where the submit button was clicked + const form = event.target; const submitter = event.submitter; const that = this; diff --git a/assets/controllers/elements/ipn_suggestion_controller.js b/assets/controllers/elements/ipn_suggestion_controller.js new file mode 100644 index 00000000..c8b543cb --- /dev/null +++ b/assets/controllers/elements/ipn_suggestion_controller.js @@ -0,0 +1,250 @@ +import { Controller } from "@hotwired/stimulus"; +import "../../css/components/autocomplete_bootstrap_theme.css"; + +export default class extends Controller { + static targets = ["input"]; + static values = { + partId: Number, + partCategoryId: Number, + partDescription: String, + suggestions: Object, + commonSectionHeader: String, // Dynamic header for common Prefixes + partIncrementHeader: String, // Dynamic header for new possible part increment + suggestUrl: String, + }; + + connect() { + this.configureAutocomplete(); + this.watchCategoryChanges(); + this.watchDescriptionChanges(); + } + + templates = { + commonSectionHeader({ title, html }) { + return html` +
+
+ ${title} +
+
+
+ `; + }, + partIncrementHeader({ title, html }) { + return html` +
+
+ ${title} +
+
+
+ `; + }, + list({ html }) { + return html` +
    + `; + }, + item({ suggestion, description, html }) { + return html` +
  • +
    +
    +
    + + + +
    +
    +
    ${suggestion}
    +
    ${description}
    +
    +
    +
    +
  • + `; + }, + }; + + configureAutocomplete() { + const inputField = this.inputTarget; + const commonPrefixes = this.suggestionsValue.commonPrefixes || []; + const prefixesPartIncrement = this.suggestionsValue.prefixesPartIncrement || []; + const commonHeader = this.commonSectionHeaderValue; + const partIncrementHeader = this.partIncrementHeaderValue; + + if (!inputField || (!commonPrefixes.length && !prefixesPartIncrement.length)) return; + + // Check whether the panel should be created at the update + if (this.isPanelInitialized) { + const existingPanel = inputField.parentNode.querySelector(".aa-Panel"); + if (existingPanel) { + // Only remove the panel in the update phase + + existingPanel.remove(); + } + } + + // Create panel + const panel = document.createElement("div"); + panel.classList.add("aa-Panel"); + panel.style.display = "none"; + + // Create panel layout + const panelLayout = document.createElement("div"); + panelLayout.classList.add("aa-PanelLayout", "aa-Panel--scrollable"); + + // Section for prefixes part increment + if (prefixesPartIncrement.length) { + const partIncrementSection = document.createElement("section"); + partIncrementSection.classList.add("aa-Source"); + + const partIncrementHeaderHtml = this.templates.partIncrementHeader({ + title: partIncrementHeader, + html: String.raw, + }); + partIncrementSection.innerHTML += partIncrementHeaderHtml; + + const partIncrementList = document.createElement("ul"); + partIncrementList.classList.add("aa-List"); + partIncrementList.setAttribute("role", "listbox"); + + prefixesPartIncrement.forEach((prefix) => { + const itemHTML = this.templates.item({ + suggestion: prefix.title, + description: prefix.description, + html: String.raw, + }); + partIncrementList.innerHTML += itemHTML; + }); + + partIncrementSection.appendChild(partIncrementList); + panelLayout.appendChild(partIncrementSection); + } + + // Section for common prefixes + if (commonPrefixes.length) { + const commonSection = document.createElement("section"); + commonSection.classList.add("aa-Source"); + + const commonSectionHeader = this.templates.commonSectionHeader({ + title: commonHeader, + html: String.raw, + }); + commonSection.innerHTML += commonSectionHeader; + + const commonList = document.createElement("ul"); + commonList.classList.add("aa-List"); + commonList.setAttribute("role", "listbox"); + + commonPrefixes.forEach((prefix) => { + const itemHTML = this.templates.item({ + suggestion: prefix.title, + description: prefix.description, + html: String.raw, + }); + commonList.innerHTML += itemHTML; + }); + + commonSection.appendChild(commonList); + panelLayout.appendChild(commonSection); + } + + panel.appendChild(panelLayout); + inputField.parentNode.appendChild(panel); + + inputField.addEventListener("focus", () => { + panel.style.display = "block"; + }); + + inputField.addEventListener("blur", () => { + setTimeout(() => { + panel.style.display = "none"; + }, 100); + }); + + // Selection of an item + panelLayout.addEventListener("mousedown", (event) => { + const target = event.target.closest("li"); + + if (target) { + inputField.value = target.dataset.suggestion; + panel.style.display = "none"; + } + }); + + this.isPanelInitialized = true; + }; + + watchCategoryChanges() { + const categoryField = document.querySelector('[data-ipn-suggestion="categoryField"]'); + const descriptionField = document.querySelector('[data-ipn-suggestion="descriptionField"]'); + this.previousCategoryId = Number(this.partCategoryIdValue); + + if (categoryField) { + categoryField.addEventListener("change", () => { + const categoryId = Number(categoryField.value); + const description = String(descriptionField?.value ?? ''); + + // Check whether the category has changed compared to the previous ID + if (categoryId !== this.previousCategoryId) { + this.fetchNewSuggestions(categoryId, description); + this.previousCategoryId = categoryId; + } + }); + } + } + + watchDescriptionChanges() { + const categoryField = document.querySelector('[data-ipn-suggestion="categoryField"]'); + const descriptionField = document.querySelector('[data-ipn-suggestion="descriptionField"]'); + this.previousDescription = String(this.partDescriptionValue); + + if (descriptionField) { + descriptionField.addEventListener("input", () => { + const categoryId = Number(categoryField.value); + const description = String(descriptionField?.value ?? ''); + + // Check whether the description has changed compared to the previous one + if (description !== this.previousDescription) { + this.fetchNewSuggestions(categoryId, description); + this.previousDescription = description; + } + }); + } + } + + fetchNewSuggestions(categoryId, description) { + const baseUrl = this.suggestUrlValue; + const partId = this.partIdValue; + const truncatedDescription = description.length > 150 ? description.substring(0, 150) : description; + const encodedDescription = this.base64EncodeUtf8(truncatedDescription); + const url = `${baseUrl}?partId=${partId}&categoryId=${categoryId}` + (description !== '' ? `&description=${encodedDescription}` : ''); + + fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + "Accept": "application/json", + }, + }) + .then((response) => { + if (!response.ok) { + throw new Error(`Error when calling up the IPN-suggestions: ${response.status}`); + } + return response.json(); + }) + .then((data) => { + this.suggestionsValue = data; + this.configureAutocomplete(); + }) + .catch((error) => { + console.error("Errors when loading the new IPN-suggestions:", error); + }); + }; + + base64EncodeUtf8(text) { + const utf8Bytes = new TextEncoder().encode(text); + return btoa(String.fromCharCode(...utf8Bytes)); + }; +} diff --git a/assets/controllers/elements/localStorage_checkbox_controller.js b/assets/controllers/elements/localStorage_checkbox_controller.js new file mode 100644 index 00000000..70ef877d --- /dev/null +++ b/assets/controllers/elements/localStorage_checkbox_controller.js @@ -0,0 +1,67 @@ +/* + * 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 . + */ + +import {Controller} from "@hotwired/stimulus"; + +export default class extends Controller +{ + static values = { + id: String + } + + connect() { + this.loadState() + this.element.addEventListener('change', () => { + this.saveState() + }); + } + + loadState() { + let storageKey = this.getStorageKey(); + let value = localStorage.getItem(storageKey); + if (value === null) { + return; + } + + if (value === 'true') { + this.element.checked = true + } + if (value === 'false') { + this.element.checked = false + } + } + + saveState() { + let storageKey = this.getStorageKey(); + + if (this.element.checked) { + localStorage.setItem(storageKey, 'true'); + } else { + localStorage.setItem(storageKey, 'false'); + } + } + + getStorageKey() { + if (this.hasIdValue) { + return 'persistent_checkbox_' + this.idValue + } + + return 'persistent_checkbox_' + this.element.id; + } +} diff --git a/assets/controllers/elements/part_search_controller.js b/assets/controllers/elements/part_search_controller.js new file mode 100644 index 00000000..c33cece0 --- /dev/null +++ b/assets/controllers/elements/part_search_controller.js @@ -0,0 +1,200 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2024 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 { autocomplete } from '@algolia/autocomplete-js'; +//import "@algolia/autocomplete-theme-classic/dist/theme.css"; +import "../../css/components/autocomplete_bootstrap_theme.css"; +import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; +import {marked} from "marked"; + +import { + trans, + SEARCH_PLACEHOLDER, + SEARCH_SUBMIT, + STATISTICS_PARTS +} from '../../translator'; + + +/** + * This controller is responsible for the search fields in the navbar and the homepage. + * It uses the Algolia Autocomplete library to provide a fast and responsive search. + */ +export default class extends Controller { + + static targets = ["input"]; + + _autocomplete; + + // Highlight the search query in the results + _highlight = (text, query) => { + if (!text) return text; + if (!query) return text; + + const HIGHLIGHT_PRE_TAG = '__aa-highlight__' + const HIGHLIGHT_POST_TAG = '__/aa-highlight__' + + const escaped = query.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); + const regex = new RegExp(escaped, 'gi'); + + return text.replace(regex, (match) => `${HIGHLIGHT_PRE_TAG}${match}${HIGHLIGHT_POST_TAG}`); + } + + initialize() { + // The endpoint for searching parts + const base_url = this.element.dataset.autocomplete; + // The URL template for the part detail pages + const part_detail_uri_template = this.element.dataset.detailUrl; + + //The URL of the placeholder picture + const placeholder_image = this.element.dataset.placeholderImage; + + //If the element is in navbar mode, or not + const navbar_mode = this.element.dataset.navbarMode === "true"; + + const that = this; + + const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ + key: 'RECENT_SEARCH', + limit: 5, + }); + + this._autocomplete = autocomplete({ + container: this.element, + //Place the panel in the navbar, if the element is in navbar mode + panelContainer: navbar_mode ? document.getElementById("navbar-search-form") : document.body, + panelPlacement: this.element.dataset.panelPlacement, + plugins: [recentSearchesPlugin], + openOnFocus: true, + placeholder: trans(SEARCH_PLACEHOLDER), + translations: { + submitButtonTitle: trans(SEARCH_SUBMIT) + }, + + // Use a navigator compatible with turbo: + navigator: { + navigate({ itemUrl }) { + window.Turbo.visit(itemUrl, { action: "advance" }); + }, + navigateNewTab({ itemUrl }) { + const windowReference = window.open(itemUrl, '_blank', 'noopener'); + + if (windowReference) { + windowReference.focus(); + } + }, + navigateNewWindow({ itemUrl }) { + window.open(itemUrl, '_blank', 'noopener'); + }, + }, + + // If the form is submitted, forward the term to the form + onSubmit({state, event, ...setters}) { + //Put the current text into each target input field + const input = that.inputTarget; + + if (!input) { + return; + } + + //Do not submit the form, if the input is empty + if (state.query === "") { + return; + } + + input.value = state.query; + input.form.requestSubmit(); + }, + + + getSources({ query }) { + return [ + // The parts source + { + sourceId: 'parts', + getItems() { + const url = base_url.replace('__QUERY__', encodeURIComponent(query)); + + const data = fetch(url) + .then((response) => response.json()) + ; + + //Iterate over all fields besides the id and highlight them + const fields = ["name", "description", "category", "footprint"]; + + data.then((items) => { + items.forEach((item) => { + for (const field of fields) { + item[field] = that._highlight(item[field], query); + } + }); + }); + + return data; + }, + getItemUrl({ item }) { + return part_detail_uri_template.replace('__ID__', item.id); + }, + templates: { + header({ html }) { + return html`${trans(STATISTICS_PARTS)} +
    `; + }, + item({item, components, html}) { + const details_url = part_detail_uri_template.replace('__ID__', item.id); + + return html` + +
    +
    + ${item.name} +
    +
    +
    + + ${components.Highlight({hit: item, attribute: 'name'})} + +
    +
    + ${components.Highlight({hit: item, attribute: 'description'})} + ${item.category ? html`

    ${components.Highlight({hit: item, attribute: 'category'})}

    ` : ""} + ${item.footprint ? html`

    ${components.Highlight({hit: item, attribute: 'footprint'})}

    ` : ""} +
    +
    +
    +
    + `; + }, + }, + }, + ]; + }, + }); + + //Try to find the input field and register a defocus handler. This is necessarry, as by default the autocomplete + //lib has problems when multiple inputs are present on the page. (see https://github.com/algolia/autocomplete/issues/1216) + const inputs = this.element.getElementsByClassName('aa-Input'); + for (const input of inputs) { + input.addEventListener('blur', () => { + this._autocomplete.setIsOpen(false); + }); + } + + } +} \ No newline at end of file diff --git a/assets/controllers/elements/part_select_controller.js b/assets/controllers/elements/part_select_controller.js index c7507636..8a4e19b8 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) => { @@ -27,7 +34,7 @@ export default class extends Controller { } let tmp = '
    ' + - "
    " + + "
    " + (data.image ? "" : "") + "
    " + "
    " + @@ -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 a96bca10..d70e588c 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 85680af0..17e85fae 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 new file mode 100644 index 00000000..9703c618 --- /dev/null +++ b/assets/controllers/elements/static_file_autocomplete_controller.js @@ -0,0 +1,112 @@ +/* + * 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 . + */ + +import {Controller} from "@hotwired/stimulus"; + +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. + * The file is just a list of strings, one per line, which will be used as the autocomplete options. + * Lines starting with # will be ignored. + */ +export default class extends Controller { + _tomSelect; + + connect() { + + let dropdownParent = "body"; + if (this.element.closest('.modal')) { + dropdownParent = null + } + + let settings = { + persistent: false, + create: true, + maxItems: 1, + maxOptions: 100, + createOnBlur: true, + selectOnTab: true, + 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', + plugins: { + 'autoselect_typed': {}, + 'click_to_edit': {}, + 'clear_button': {}, + 'restore_on_backspace': {} + } + }; + + if (this.element.dataset.url) { + const url = this.element.dataset.url; + settings.load = (query, callback) => { + const self = this; + if (self.loading > 1) { + callback(); + return; + } + + fetch(url) + .then(response => response.text()) + .then(text => { + // Convert the text file to array + let lines = text.split("\n"); + //Remove all lines beginning with # + lines = lines.filter(x => !x.startsWith("#")); + + //Convert the array to an object, where each line is in the text field + lines = lines.map(x => { + return {text: x}; + }); + + + //Unset the load function to prevent endless recursion + self._tomSelect.settings.load = null; + + callback(lines); + }).catch(() => { + callback(); + }); + }; + } + + this._tomSelect = new TomSelect(this.element, settings); + } + + disconnect() { + super.disconnect(); + //Destroy the TomSelect instance + this._tomSelect.destroy(); + } + +} diff --git a/assets/controllers/elements/structural_entity_select_controller.js b/assets/controllers/elements/structural_entity_select_controller.js index e775af8a..4b220d5b 100644 --- a/assets/controllers/elements/structural_entity_select_controller.js +++ b/assets/controllers/elements/structural_entity_select_controller.js @@ -24,6 +24,8 @@ 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; @@ -38,12 +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: /\D/, //Must contain a non-digit character, otherwise they would be recognized as DB ID + 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}, @@ -54,7 +68,21 @@ export default class extends Controller { render: { item: this.renderItem.bind(this), option: this.renderOption.bind(this), - option_create: function(data, escape) { + option_create: (data, escape) => { + //If the input starts with "->", we prepend the current selected value, for easier extension of existing values + //This here handles the display part, while the createItem function handles the actual creation + if (data.input.startsWith("->")) { + //Get current selected value + const current = this._tomSelect.getItem(this._tomSelect.getValue()).textContent.replaceAll("→", "->").trim(); + //Prepend it to the input + if (current) { + data.input = current + " " + data.input; + } else { + //If there is no current value, we remove the "->" + data.input = data.input.substring(2); + } + } + return '
     ' + escape(data.input) + '… ' + '(' + addHint +')' + '
    '; @@ -64,20 +92,72 @@ 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); - this._tomSelect.sync(); + //Do not do a sync here as this breaks the initial rendering of the empty option + //this._tomSelect.sync(); } createItem(input, callback) { + + //If the input starts with "->", we prepend the current selected value, for easier extension of existing values + if (input.startsWith("->")) { + //Get current selected value + let current = this._tomSelect.getItem(this._tomSelect.getValue()).textContent.replaceAll("→", "->").trim(); + //Replace no break spaces with normal spaces + current = current.replaceAll("\u00A0", " "); + //Prepend it to the input + if (current) { + input = current + " " + input; + } else { + //If there is no current value, we remove the "->" + input = input.substring(2); + } + } + callback({ - value: input, + //$%$ is a special value prefix, that is used to identify items, that are not yet in the DB + value: '$%$' + input, text: input, not_in_db_yet: true, }); } + 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 acfcd0fa..14725227 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/elements/tree_controller.js b/assets/controllers/elements/tree_controller.js index d2f21a8e..bb64839c 100644 --- a/assets/controllers/elements/tree_controller.js +++ b/assets/controllers/elements/tree_controller.js @@ -94,13 +94,15 @@ export default class extends Controller { showTags: this._showTags, data: data, showIcon: true, + preventUnselect: true, + allowReselect: true, onNodeSelected: (event) => { const node = event.detail.node; if (node.href) { window.Turbo.visit(node.href, {action: "advance"}); + this._registerURLWatcher(node); } }, - //onNodeContextmenu: contextmenu_handler, }, [BS5Theme, BS53Theme, FAIconTheme]); this.treeTarget.addEventListener(EVENT_INITIALIZED, (event) => { @@ -108,12 +110,42 @@ export default class extends Controller { const treeView = event.detail.treeView; treeView.revealNode(treeView.getSelected()); + //Add the url watcher to all selected nodes + for (const node of treeView.getSelected()) { + this._registerURLWatcher(node); + } + //Add contextmenu event listener to the tree, which allows us to open the links in a new tab with a right click treeView.getTreeElement().addEventListener("contextmenu", this._onContextMenu.bind(this)); }); } + _registerURLWatcher(node) + { + //Register a watcher for a location change, which will unselect the node, if the location changes + const desired_url = node.href; + + //Ensure that the node is unselected, if the location changes + const unselectNode = () => { + //Parse url so we can properly compare them + const desired = new URL(node.href, window.location.origin); + + //We only compare the pathname, because the hash and parameters should not matter + if(window.location.pathname !== desired.pathname) { + //The ignore parameter is important here, otherwise the node will not be unselected + node.setSelected(false, {silent: true, ignorePreventUnselect: true}); + + //Unregister the watcher + document.removeEventListener('turbo:load', unselectNode); + } + }; + + //Register the watcher via hotwire turbo + //We must just load to have the new url in window.location + document.addEventListener('turbo:load', unselectNode); + } + _onContextMenu(event) { //Find the node that was clicked and open link in new tab diff --git a/assets/controllers/field_mapping_controller.js b/assets/controllers/field_mapping_controller.js new file mode 100644 index 00000000..9c9c8ac6 --- /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="click->field-mapping#addMapping") + // No manual event listener needed + + // Form submit handler + const form = this.element.querySelector('form') + if (form && this.hasSubmitButtonTarget) { + form.addEventListener('submit', this.handleFormSubmit.bind(this)) + } + } + + addMapping() { + const currentMappings = this.tbodyTarget.querySelectorAll('.mapping-row').length + + if (currentMappings >= this.maxMappingsValue) { + alert(this.maxMappingsReachedMessageValue) + return + } + + const newRowHtml = this.prototypeValue.replace(/__name__/g, this.mappingIndexValue) + const tempDiv = document.createElement('div') + tempDiv.innerHTML = newRowHtml + + const fieldWidget = tempDiv.querySelector('select[name*="[field]"]') || tempDiv.children[0] + const providerWidget = tempDiv.querySelector('select[name*="[providers]"]') || tempDiv.children[1] + const priorityWidget = tempDiv.querySelector('input[name*="[priority]"]') || tempDiv.children[2] + + const newRow = document.createElement('tr') + newRow.className = 'mapping-row' + newRow.innerHTML = ` + ${fieldWidget ? fieldWidget.outerHTML : ''} + ${providerWidget ? providerWidget.outerHTML : ''} + ${priorityWidget ? priorityWidget.outerHTML : ''} + + + + ` + + this.tbodyTarget.appendChild(newRow) + this.mappingIndexValue++ + + const newFieldSelect = newRow.querySelector('select[name*="[field]"]') + if (newFieldSelect) { + newFieldSelect.value = '' + newFieldSelect.addEventListener('change', this.updateFieldOptions.bind(this)) + } + + this.updateFieldOptions() + this.updateAddButtonState() + } + + removeMapping(event) { + const row = event.target.closest('tr') + row.remove() + this.updateFieldOptions() + this.updateAddButtonState() + } + + updateFieldOptions() { + const fieldSelects = this.tbodyTarget.querySelectorAll('select[name*="[field]"]') + + const selectedFields = Array.from(fieldSelects) + .map(select => select.value) + .filter(value => value && value !== '') + + fieldSelects.forEach(select => { + Array.from(select.options).forEach(option => { + const isCurrentValue = option.value === select.value + const isEmptyOption = !option.value || option.value === '' + const isAlreadySelected = selectedFields.includes(option.value) + + if (!isEmptyOption && isAlreadySelected && !isCurrentValue) { + option.disabled = true + option.style.display = 'none' + } else { + option.disabled = false + option.style.display = '' + } + }) + }) + } + + updateAddButtonState() { + const currentMappings = this.tbodyTarget.querySelectorAll('.mapping-row').length + + if (this.hasAddButtonTarget) { + if (currentMappings >= this.maxMappingsValue) { + this.addButtonTarget.disabled = true + this.addButtonTarget.title = this.maxMappingsReachedMessageValue + } else { + this.addButtonTarget.disabled = false + this.addButtonTarget.title = '' + } + } + } + + handleFormSubmit(event) { + if (this.hasSubmitButtonTarget) { + this.submitButtonTarget.disabled = true + + // Disable the entire form to prevent changes during processing + const form = event.target + const formElements = form.querySelectorAll('input, select, textarea, button') + formElements.forEach(element => { + if (element !== this.submitButtonTarget) { + element.disabled = true + } + }) + } + } +} \ No newline at end of file diff --git a/assets/controllers/pages/association_edit_type_select_controller.js b/assets/controllers/pages/association_edit_type_select_controller.js new file mode 100644 index 00000000..10badf9c --- /dev/null +++ b/assets/controllers/pages/association_edit_type_select_controller.js @@ -0,0 +1,44 @@ +/* + * 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 . + */ + +import {Controller} from "@hotwired/stimulus"; + +export default class extends Controller { + + static targets = [ "display", "select" ] + + connect() + { + this.update(); + this.selectTarget.addEventListener('change', this.update.bind(this)); + } + + update() + { + //If the select value is 0, then we show the input field + if( this.selectTarget.value === '0') + { + this.displayTarget.classList.remove('d-none'); + } + else + { + this.displayTarget.classList.add('d-none'); + } + } +} \ No newline at end of file diff --git a/assets/controllers/pages/barcode_scan_controller.js b/assets/controllers/pages/barcode_scan_controller.js index 5f82a39e..368fef43 100644 --- a/assets/controllers/pages/barcode_scan_controller.js +++ b/assets/controllers/pages/barcode_scan_controller.js @@ -20,7 +20,7 @@ import {Controller} from "@hotwired/stimulus"; //import * as ZXing from "@zxing/library"; -import {Html5QrcodeScanner, Html5Qrcode} from "html5-qrcode"; +import {Html5QrcodeScanner, Html5Qrcode} from "@part-db/html5-qrcode"; /* stimulusFetch: 'lazy' */ export default class extends Controller { @@ -50,7 +50,7 @@ export default class extends Controller { }); this._scanner = new Html5QrcodeScanner(this.element.id, { - fps: 2, + fps: 10, qrbox: qrboxFunction, experimentalFeatures: { //This option improves reading quality on android chrome @@ -61,6 +61,11 @@ export default class extends Controller { this._scanner.render(this.onScanSuccess.bind(this)); } + disconnect() { + this._scanner.pause(); + this._scanner.clear(); + } + onScanSuccess(decodedText, decodedResult) { //Put our decoded Text into the input box document.getElementById('scan_dialog_input').value = decodedText; diff --git a/assets/controllers/pages/latex_preview_controller.js b/assets/controllers/pages/latex_preview_controller.js index c836faa6..7f1e611c 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 f6504990..e187aa42 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/pages/part_merge_modal_controller.js b/assets/controllers/pages/part_merge_modal_controller.js new file mode 100644 index 00000000..e9e41302 --- /dev/null +++ b/assets/controllers/pages/part_merge_modal_controller.js @@ -0,0 +1,68 @@ +/* + * 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 . + */ + +import {Controller} from "@hotwired/stimulus"; + +export default class extends Controller +{ + static targets = ['link', 'mode', 'otherSelect']; + static values = { + targetId: Number, + }; + + connect() { + } + + update() { + const link = this.linkTarget; + const other_select = this.otherSelectTarget; + + //Extract the mode using the mode radio buttons (we filter the array to get the checked one) + const mode = (this.modeTargets.filter((e)=>e.checked))[0].value; + + if (other_select.value === '') { + link.classList.add('disabled'); + return; + } + + //Extract href template from data attribute on link target + let href = link.getAttribute('data-href-template'); + + let target, other; + if (mode === '1') { + target = this.targetIdValue; + other = other_select.value; + } else if (mode === '2') { + target = other_select.value; + other = this.targetIdValue; + } else { + throw 'Invalid mode'; + } + + //Replace placeholder with actual target id + href = href.replace('__target__', target); + //Replace placeholder with selected value of the select (the event sender) + href = href.replace('__other__', other); + + //Assign new href to link + link.setAttribute('href', href); + //Make link clickable + link.classList.remove('disabled'); + } +} \ No newline at end of file diff --git a/assets/controllers/pages/synonyms_collection_controller.js b/assets/controllers/pages/synonyms_collection_controller.js new file mode 100644 index 00000000..6b2f4811 --- /dev/null +++ b/assets/controllers/pages/synonyms_collection_controller.js @@ -0,0 +1,68 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { Controller } from '@hotwired/stimulus'; + +export default class extends Controller { + static targets = ['items']; + static values = { + prototype: String, + prototypeName: { type: String, default: '__name__' }, + index: { type: Number, default: 0 }, + }; + + connect() { + if (!this.hasIndexValue || Number.isNaN(this.indexValue)) { + this.indexValue = this.itemsTarget?.children.length || 0; + } + } + + add(event) { + event.preventDefault(); + + const encodedProto = this.prototypeValue || ''; + const placeholder = this.prototypeNameValue || '__name__'; + if (!encodedProto || !this.itemsTarget) return; + + const protoHtml = this._decodeHtmlAttribute(encodedProto); + + const idx = this.indexValue; + const html = protoHtml.replace(new RegExp(placeholder, 'g'), String(idx)); + + const wrapper = document.createElement('div'); + wrapper.innerHTML = html; + const newItem = wrapper.firstElementChild; + if (newItem) { + this.itemsTarget.appendChild(newItem); + this.indexValue = idx + 1; + } + } + + remove(event) { + event.preventDefault(); + const row = event.currentTarget.closest('.tc-item'); + if (row) row.remove(); + } + + _decodeHtmlAttribute(str) { + const tmp = document.createElement('textarea'); + tmp.innerHTML = str; + return tmp.value || tmp.textContent || ''; + } +} diff --git a/assets/controllers/toggle_password_controller.js b/assets/controllers/toggle_password_controller.js new file mode 100644 index 00000000..bef87e11 --- /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 070f353d..ec5a8f7c 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 2741d667..8e7b6fa3 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 9168484d..0212a85b 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,9 +49,8 @@ } .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; - width: 100%; } .part-info-image { @@ -61,4 +60,4 @@ .object-fit-cover { object-fit: cover; -} \ No newline at end of file +} diff --git a/assets/css/app/layout.css b/assets/css/app/layout.css index e00c823c..58808926 100644 --- a/assets/css/app/layout.css +++ b/assets/css/app/layout.css @@ -108,8 +108,8 @@ body { .back-to-top { cursor: pointer; position: fixed; - bottom: 20px; - right: 20px; + bottom: 60px; + right: 40px; display:none; z-index: 1030; } @@ -133,7 +133,7 @@ showing the sidebar (on devices with md or higher) */ #sidebar-toggle-button { position: fixed; - left: 3px; + left: 2px; bottom: 50%; } diff --git a/assets/css/app/tables.css b/assets/css/app/tables.css index bc097726..b2d8882c 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 ****************************************/ @@ -63,10 +73,6 @@ table.dataTable > tbody > tr.selected > td > a { margin-block-end: 0; } -.card-footer-table { - padding-top: 0; -} - table.dataTable { margin-top: 0 !important; } @@ -84,16 +90,31 @@ th.select-checkbox { * Datatables definitions/overrides ********************************************************************/ -.dataTables_length { +.dt-length { 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 { margin-top: -20px !important; } +/** Show pagination right aligned */ +.dt-paging .pagination { + justify-content: flex-end; +} + +/** Fix table row coloring */ +table.table.dataTable > :not(caption) > * > * { + background-color: var(--bs-table-bg); +} + /****************************************************** Classes for Datatables export @@ -104,52 +125,3 @@ Classes for Datatables export .export-helper{ display: none; } - -/****************************************************** - * Styling for the select all checkbox in the parts table - * Should match the styling of the select checkbox - ******************************************************/ -table.dataTable > thead > tr > th.select-checkbox { - position: relative; -} -table.dataTable > thead > tr > th.select-checkbox:before, -table.dataTable > thead > tr > th.select-checkbox:after { - display: block; - position: absolute; - top: 0.9em; - left: 50%; - width: 1em !important; - height: 1em !important; - box-sizing: border-box; -} -table.dataTable > thead > tr > th.select-checkbox:before { - content: " "; - margin-top: -5px; - margin-left: -6px; - border: 2px solid var(--bs-tertiary-color); - border-radius: 3px; -} - -table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:before { - border: 2px solid var(--bs-tertiary-color) !important; -} - -table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > td.select-checkbox:after, table.dataTable > tbody > tr > th.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:after { - width: 1em !important; - height: 1em !important; -} - -table.dataTable > thead > tr.selected > th.select-checkbox:after { - content: "✓"; - font-size: 20px; - margin-top: -20px; - margin-left: -6px; - text-align: center; - /*text-shadow: 1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9; */ -} -table.dataTable.compact > thead > tr > th.select-checkbox:before { - margin-top: -12px; -} -table.dataTable.compact > thead > tr.selected > th.select-checkbox:after { - margin-top: -16px; -} diff --git a/assets/css/components/autocomplete_bootstrap_theme.css b/assets/css/components/autocomplete_bootstrap_theme.css new file mode 100644 index 00000000..d86232e5 --- /dev/null +++ b/assets/css/components/autocomplete_bootstrap_theme.css @@ -0,0 +1,1120 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2024 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 . + */ + +/*** + * This file is based on the autocomplete-theme-classic from Algolia and modifies it to fit better into the bootstrap 5 + * theme of Part-DB. + */ + +/*! @algolia/autocomplete-theme-classic 1.17.0 | MIT License | © Algolia, Inc. and contributors | https://github.com/algolia/autocomplete */ +/* ----------------*/ +/* 1. CSS Variables*/ +/* 2. Dark Mode*/ +/* 3. Autocomplete*/ +/* 4. Panel*/ +/* 5. Sources*/ +/* 6. Hit Layout*/ +/* 7. Panel Header*/ +/* 8. Panel Footer*/ +/* 9. Detached Mode*/ +/* 10. Gradients*/ +/* 11. Utilities*/ +/* ----------------*/ +/* Note:*/ +/* This theme reflects the markup structure of autocomplete with SCSS indentation.*/ +/* We use the SASS `@at-root` function to keep specificity low.*/ +/* ----------------*/ +/* 1. CSS Variables*/ +/* ----------------*/ +:root { + /* Input*/ + --aa-search-input-height: 44px; + --aa-input-icon-size: 20px; + /* Size and spacing*/ + --aa-base-unit: 16; + --aa-spacing-factor: 1; + --aa-spacing: calc(var(--aa-base-unit) * var(--aa-spacing-factor) * 1px); + --aa-spacing-half: calc(var(--aa-spacing) / 2); + --aa-panel-max-height: 650px; + /* Z-index*/ + --aa-base-z-index: 9999; + /* Font*/ + --aa-font-size: calc(var(--aa-base-unit) * 1px); + --aa-font-family: inherit; + --aa-font-weight-medium: 500; + --aa-font-weight-semibold: 600; + --aa-font-weight-bold: 700; + /* Icons*/ + --aa-icon-size: 20px; + --aa-icon-stroke-width: 1.6; + --aa-icon-color-rgb: 119, 119, 163; + --aa-icon-color-alpha: 1; + --aa-action-icon-size: 20px; + /* Text colors*/ + --aa-text-color-rgb: 38, 38, 39; + --aa-text-color-alpha: 1; + --aa-primary-color-rgb: 62, 52, 211; + --aa-primary-color-alpha: 0.2; + --aa-muted-color-rgb: 128, 126, 163; + --aa-muted-color-alpha: 0.6; + /* Border colors*/ + --aa-panel-border-color-rgb: 128, 126, 163; + --aa-panel-border-color-alpha: 0.3; + --aa-input-border-color-rgb: 128, 126, 163; + --aa-input-border-color-alpha: 0.8; + /* Background colors*/ + --aa-background-color-rgb: 255, 255, 255; + --aa-background-color-alpha: 1; + --aa-input-background-color-rgb: 255, 255, 255; + --aa-input-background-color-alpha: 1; + --aa-selected-color-rgb: 179, 173, 214; + --aa-selected-color-alpha: 0.205; + --aa-description-highlight-background-color-rgb: 245, 223, 77; + --aa-description-highlight-background-color-alpha: 0.5; + /* Detached mode*/ + --aa-detached-media-query: (max-width: 680px); + --aa-detached-modal-media-query: (min-width: 680px); + --aa-detached-modal-max-width: 680px; + --aa-detached-modal-max-height: 500px; + --aa-overlay-color-rgb: 115, 114, 129; + --aa-overlay-color-alpha: 0.4; + /* Shadows*/ + --aa-panel-shadow: 0 0 0 1px rgba(35, 38, 59, .1), + 0 6px 16px -4px rgba(35, 38, 59, .15); + /* Scrollbar*/ + --aa-scrollbar-width: 13px; + --aa-scrollbar-track-background-color-rgb: 234, 234, 234; + --aa-scrollbar-track-background-color-alpha: 1; + --aa-scrollbar-thumb-background-color-rgb: var(--aa-background-color-rgb); + --aa-scrollbar-thumb-background-color-alpha: 1; + /* Touch screens*/ +} +@media (hover: none) and (pointer: coarse) { + :root { + --aa-spacing-factor: 1.2; + --aa-action-icon-size: 22px; + } +} + +/* ----------------*/ +/* 2. Dark Mode*/ +/* ----------------*/ +body { + /* stylelint-disable selector-no-qualifying-type, selector-class-pattern */ + /* stylelint-enable selector-no-qualifying-type, selector-class-pattern */ +} + +/* Reset for `@extend`*/ +.aa-Panel *, .aa-Autocomplete *, +.aa-DetachedFormContainer * { + box-sizing: border-box; +} + +/* Init for `@extend`*/ +.aa-Panel, .aa-Autocomplete, +.aa-DetachedFormContainer { + color: rgba(var(--aa-text-color-rgb), var(--aa-text-color-alpha)); + color: var(--bs-body-color); + font-family: inherit; + font-weight: normal; + line-height: 1em; + margin: 0; + padding: 0; + text-align: left; +} + +/* ----------------*/ +/* 3. Autocomplete*/ +/* ----------------*/ +.aa-Autocomplete, +.aa-DetachedFormContainer { + /* Search box*/ +} +.aa-Form { + align-items: center; + background-color: var(--bs-body-bg); + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + color: var(--bs-body-color); + transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; + display: flex; + line-height: 1em; + margin: 0; + position: relative; + width: 100%; +} +.aa-Form:focus-within { + background-color: var(--bs-body-bg); + border-color: #86b7fe; + box-shadow: 0 0 0 0.25rem rgba(13,110,253,.25); + color: var(--bs-body-color); + outline: 0; +} +.aa-InputWrapperPrefix { + align-items: center; + display: flex; + flex-shrink: 0; + height: 44px; + height: var(--aa-search-input-height); + order: 1; + /* Container for search and loading icons*/ +} +.aa-Label, +.aa-LoadingIndicator { + cursor: auto; + cursor: initial; + flex-shrink: 0; + height: 100%; + padding: 0; + text-align: left; +} +.aa-Label svg, +.aa-LoadingIndicator svg { + color: rgba(var(--bs-primary-rgb), 1.0); + height: auto; + max-height: 20px; + max-height: var(--aa-input-icon-size); + stroke-width: 1.6; + stroke-width: var(--aa-icon-stroke-width); + width: 20px; + width: var(--aa-input-icon-size); +} + +.aa-SubmitButton, +.aa-LoadingIndicator { + height: 100%; + padding-left: calc((16 * 1 * 1px) * 0.75 - 1px); + padding-left: calc(calc(16 * 1 * 1px) * 0.75 - 1px); + padding-left: calc(var(--aa-spacing) * 0.75 - 1px); + padding-right: calc((16 * 1 * 1px) / 2); + padding-right: calc(calc(16 * 1 * 1px) / 2); + padding-right: var(--aa-spacing-half); + width: calc((16 * 1 * 1px) * 1.75 + 20px - 1px); + width: calc(calc(16 * 1 * 1px) * 1.75 + 20px - 1px); + width: calc(var(--aa-spacing) * 1.75 + var(--aa-icon-size) - 1px); +} +@media (hover: none) and (pointer: coarse) { + .aa-SubmitButton, + .aa-LoadingIndicator { + padding-left: calc(((16 * 1 * 1px) / 2) / 2 - 1px); + padding-left: calc(calc(calc(16 * 1 * 1px) / 2) / 2 - 1px); + padding-left: calc(var(--aa-spacing-half) / 2 - 1px); + width: calc(20px + (16 * 1 * 1px) * 1.25 - 1px); + width: calc(20px + calc(16 * 1 * 1px) * 1.25 - 1px); + width: calc(var(--aa-icon-size) + var(--aa-spacing) * 1.25 - 1px); + } +} + +.aa-SubmitButton { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: none; + border: 0; + margin: 0; +} + +.aa-LoadingIndicator { + align-items: center; + display: flex; + justify-content: center; +} +.aa-LoadingIndicator[hidden] { + display: none; +} + +.aa-InputWrapper { + order: 3; + position: relative; + width: 100%; + /* Search box input (with placeholder and query)*/ +} +.aa-Input { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: none; + border: 0; + color: var(--bs-body-color); + font: inherit; + height: 44px; + height: var(--aa-search-input-height); + padding: 0; + width: 100%; + /* Focus is set and styled on the parent, it isn't necessary here*/ + /* Remove native appearance*/ +} +.aa-Input::-moz-placeholder { + color: var(--bs-secondary-color); + opacity: 1; +} +.aa-Input::placeholder { + color: var(--bs-secondary-color); + opacity: 1; +} +.aa-Input:focus { + box-shadow: none; + outline: none; +} +.aa-Input::-webkit-search-decoration, .aa-Input::-webkit-search-cancel-button, .aa-Input::-webkit-search-results-button, .aa-Input::-webkit-search-results-decoration { + -webkit-appearance: none; + appearance: none; +} + +.aa-InputWrapperSuffix { + align-items: center; + display: flex; + height: 44px; + height: var(--aa-search-input-height); + order: 4; + /* Accelerator to clear the query*/ +} +.aa-ClearButton { + align-items: center; + background: none; + border: 0; + color: var(--bs-secondary-color); + cursor: pointer; + display: flex; + height: 100%; + margin: 0; + padding: 0 calc((16 * 1 * 1px) * 0.8333333333 - 0.5px); + padding: 0 calc(calc(16 * 1 * 1px) * 0.8333333333 - 0.5px); + padding: 0 calc(var(--aa-spacing) * 0.8333333333 - 0.5px); +} +@media (hover: none) and (pointer: coarse) { + .aa-ClearButton { + padding: 0 calc((16 * 1 * 1px) * 0.6666666667 - 0.5px); + padding: 0 calc(calc(16 * 1 * 1px) * 0.6666666667 - 0.5px); + padding: 0 calc(var(--aa-spacing) * 0.6666666667 - 0.5px); + } +} +.aa-ClearButton:hover, .aa-ClearButton:focus { + color: var(--bs-body-color); +} +.aa-ClearButton[hidden] { + display: none; +} +.aa-ClearButton svg { + stroke-width: 1.6; + stroke-width: var(--aa-icon-stroke-width); + width: 20px; + width: var(--aa-icon-size); +} + +/* ----------------*/ +/* 4. Panel*/ +/* ----------------*/ +.aa-Panel { + --bs-dropdown-header-padding-x: 1rem; + --bs-dropdown-header-padding-y: 0.5rem; + --bs-dropdown-font-size: 1rem; + --bs-dropdown-color: var(--bs-body-color); + --bs-dropdown-bg: var(--bs-body-bg); + --bs-dropdown-border-color: var(--bs-border-color-translucent); + --bs-dropdown-border-radius: var(--bs-border-radius); + --bs-dropdown-border-width: var(--bs-border-width); + + z-index: 1000; + + box-shadow: 0 0 0 1px rgba(35, 38, 59, 0.1); + overflow: hidden; + position: absolute; + transition: opacity 200ms ease-in, filter 200ms ease-in; + /* When a request isn't resolved yet*/ + + padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x); + margin: 0; + font-size: var(--bs-dropdown-font-size); + color: var(--bs-dropdown-color); + background-color: var(--bs-dropdown-bg); + background-clip: padding-box; + border: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color); + border-radius: var(--bs-dropdown-border-radius); +} +@media screen and (prefers-reduced-motion) { + .aa-Panel { + transition: none; + } +} +.aa-Panel button { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: none; + border: 0; + margin: 0; + padding: 0; +} +.aa-PanelLayout { + height: 100%; + margin: 0; + max-height: 650px; + max-height: var(--aa-panel-max-height); + overflow-y: auto; + padding: 0; + position: relative; + text-align: left; +} +.aa-PanelLayoutColumns--twoGolden { + display: grid; + grid-template-columns: 39.2% auto; + overflow: hidden; + padding: 0; +} + +.aa-PanelLayoutColumns--two { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + overflow: hidden; + padding: 0; +} + +.aa-PanelLayoutColumns--three { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + overflow: hidden; + padding: 0; +} + +.aa-Panel--stalled .aa-Source { + filter: grayscale(1); + opacity: 0.8; +} + +.aa-Panel--scrollable { + margin: 0; + max-height: 650px; + max-height: var(--aa-panel-max-height); + overflow-x: hidden; + overflow-y: auto; + padding: calc((16 * 1 * 1px) / 2); + padding: calc(calc(16 * 1 * 1px) / 2); + padding: var(--aa-spacing-half); + scrollbar-color: rgba(255, 255, 255, 1) rgba(234, 234, 234, 1); + scrollbar-color: rgba(var(--aa-scrollbar-thumb-background-color-rgb), var(--aa-scrollbar-thumb-background-color-alpha)) rgba(var(--aa-scrollbar-track-background-color-rgb), var(--aa-scrollbar-track-background-color-alpha)); + scrollbar-width: thin; +} +.aa-Panel--scrollable::-webkit-scrollbar { + width: 13px; + width: var(--aa-scrollbar-width); +} +.aa-Panel--scrollable::-webkit-scrollbar-track { + background-color: rgba(234, 234, 234, 1); + background-color: rgba(var(--aa-scrollbar-track-background-color-rgb), var(--aa-scrollbar-track-background-color-alpha)); +} +.aa-Panel--scrollable::-webkit-scrollbar-thumb { + background-color: rgba(255, 255, 255, 1); + background-color: rgba(var(--aa-scrollbar-thumb-background-color-rgb), var(--aa-scrollbar-thumb-background-color-alpha)); + border-color: rgba(234, 234, 234, 1); + border-color: rgba(var(--aa-scrollbar-track-background-color-rgb), var(--aa-scrollbar-track-background-color-alpha)); + border-radius: 9999px; + border-style: solid; + border-width: 3px 2px 3px 3px; +} + +/* ----------------*/ +/* 5. Sources*/ +/* Each source can be styled independently*/ +/* ----------------*/ +.aa-Source { + margin: 0; + padding: 0; + position: relative; + width: 100%; + /* List of results inside the source*/ + /* Source title*/ + /* See all button*/ +} +.aa-Source:empty { + /* Hide empty section*/ + display: none; +} +.aa-SourceNoResults { + font-size: 1em; + margin: 0; + padding: calc(16 * 1 * 1px); + padding: var(--aa-spacing); +} + +.aa-List { + list-style: none; + margin: 0; + padding: 0; + position: relative; +} + +.aa-SourceHeader { + margin: calc((16 * 1 * 1px) / 2) 0.5em calc((16 * 1 * 1px) / 2) 0; + margin: calc(calc(16 * 1 * 1px) / 2) 0.5em calc(calc(16 * 1 * 1px) / 2) 0; + margin: var(--aa-spacing-half) 0.5em var(--aa-spacing-half) 0; + padding: 0; + position: relative; + /* Hide empty header*/ + /* Title typography*/ + /* Line separator*/ +} +.aa-SourceHeader:empty { + display: none; +} +.aa-SourceHeaderTitle { + background: var(--bs-body-bg); + color: rgba(var(--bs-primary-rgb), 1.0); + display: inline-block; + font-size: 0.8em; + font-weight: 600; + font-weight: var(--aa-font-weight-semibold); + margin: 0; + padding: 0 calc((16 * 1 * 1px) / 2) 0 0; + padding: 0 calc(calc(16 * 1 * 1px) / 2) 0 0; + padding: 0 var(--aa-spacing-half) 0 0; + position: relative; + z-index: 9999; + z-index: var(--aa-base-z-index); +} + +.aa-SourceHeaderLine { + border-bottom: solid 1px rgba(var(--bs-primary-rgb), 1.0); + display: block; + height: 2px; + left: 0; + margin: 0; + opacity: 0.3; + padding: 0; + position: absolute; + right: 0; + top: calc((16 * 1 * 1px) / 2); + top: calc(calc(16 * 1 * 1px) / 2); + top: var(--aa-spacing-half); + z-index: calc(9999 - 1); + z-index: calc(var(--aa-base-z-index) - 1); +} + +.aa-SourceFooterSeeAll { + background: linear-gradient(180deg, var(--bs-body-bg), rgba(128, 126, 163, 0.14)); + border: 1px solid var(--bs-secondary-color); + border-radius: 5px; + box-shadow: inset 0 0 2px #fff, 0 2px 2px -1px rgba(76, 69, 88, 0.15); + color: inherit; + font-size: 0.95em; + font-weight: 500; + padding: 0.475em 1em 0.6em; + -webkit-text-decoration: none; + text-decoration: none; +} +.aa-SourceFooterSeeAll:focus, .aa-SourceFooterSeeAll:hover { + border: 1px solid rgba(62, 52, 211, 1); + border: 1px solid rgba(var(--bs-primary-rgb), 1); + color: rgba(62, 52, 211, 1); + color: rgba(var(--bs-primary-rgb), 1); +} + +/* ----------------*/ +/* 6. Hit Layout*/ +/* ----------------*/ +.aa-Item { + align-items: center; + border-radius: 3px; + cursor: pointer; + display: grid; + min-height: calc((16 * 1 * 1px) * 2.5); + min-height: calc(calc(16 * 1 * 1px) * 2.5); + min-height: calc(var(--aa-spacing) * 2.5); + padding: calc(((16 * 1 * 1px) / 2) / 2); + padding: calc(calc(calc(16 * 1 * 1px) / 2) / 2); + padding: calc(var(--aa-spacing-half) / 2); + /* When the result is active*/ + /* The result type icon inlined SVG or image*/ + /* wrap hit with url but we don't need to see it*/ + /* Secondary click actions*/ +} +.aa-Item[aria-selected=true] { + background-color: var(--bs-tertiary-bg); +} +.aa-Item[aria-selected=true] .aa-ItemActionButton, +.aa-Item[aria-selected=true] .aa-ActiveOnly { + visibility: visible; +} +.aa-ItemIcon { + align-items: center; + background: var(--bs-body-bg); + border-radius: 3px; + box-shadow: inset 0 0 0 1px rgba(128, 126, 163, 0.3); + box-shadow: inset 0 0 0 1px rgba(var(--aa-panel-border-color-rgb), var(--aa-panel-border-color-alpha)); + color: rgba(119, 119, 163, 1); + color: rgba(var(--aa-icon-color-rgb), var(--aa-icon-color-alpha)); + display: flex; + flex-shrink: 0; + font-size: 0.7em; + height: calc(20px + ((16 * 1 * 1px) / 2)); + height: calc(20px + calc(calc(16 * 1 * 1px) / 2)); + height: calc(var(--aa-icon-size) + var(--aa-spacing-half)); + justify-content: center; + overflow: hidden; + stroke-width: 1.6; + stroke-width: var(--aa-icon-stroke-width); + text-align: center; + width: calc(20px + ((16 * 1 * 1px) / 2)); + width: calc(20px + calc(calc(16 * 1 * 1px) / 2)); + width: calc(var(--aa-icon-size) + var(--aa-spacing-half)); +} +.aa-ItemIcon img { + height: auto; + max-height: calc(20px + ((16 * 1 * 1px) / 2) - 8px); + max-height: calc(20px + calc(calc(16 * 1 * 1px) / 2) - 8px); + max-height: calc(var(--aa-icon-size) + var(--aa-spacing-half) - 8px); + max-width: calc(20px + ((16 * 1 * 1px) / 2) - 8px); + max-width: calc(20px + calc(calc(16 * 1 * 1px) / 2) - 8px); + max-width: calc(var(--aa-icon-size) + var(--aa-spacing-half) - 8px); + width: auto; +} +.aa-ItemIcon svg { + height: 20px; + height: var(--aa-icon-size); + width: 20px; + width: var(--aa-icon-size); +} +.aa-ItemIcon--alignTop { + align-self: flex-start; +} + +.aa-ItemIcon--noBorder { + background: none; + box-shadow: none; +} + +.aa-ItemIcon--picture { + height: 96px; + width: 96px; +} +.aa-ItemIcon--picture img { + max-height: 100%; + max-width: 100%; + padding: calc((16 * 1 * 1px) / 2); + padding: calc(calc(16 * 1 * 1px) / 2); + padding: var(--aa-spacing-half); +} + +.aa-ItemContent { + align-items: center; + cursor: pointer; + display: grid; + gap: calc((16 * 1 * 1px) / 2); + gap: calc(calc(16 * 1 * 1px) / 2); + grid-gap: calc((16 * 1 * 1px) / 2); + grid-gap: calc(calc(16 * 1 * 1px) / 2); + grid-gap: var(--aa-spacing-half); + gap: var(--aa-spacing-half); + grid-auto-flow: column; + line-height: 1.25em; + overflow: hidden; +} +.aa-ItemContent:empty { + display: none; +} +.aa-ItemContent mark { + background: var(--bs-highlight-bg); + color: var(--bs-body-color); + font-style: normal; + padding: 0; + font-weight: 700; + font-weight: var(--aa-font-weight-bold); +} +.aa-ItemContent--dual { + display: flex; + flex-direction: column; + justify-content: space-between; + text-align: left; +} +.aa-ItemContent--dual .aa-ItemContentTitle, +.aa-ItemContent--dual .aa-ItemContentSubtitle { + display: block; +} + +.aa-ItemContent--indented { + padding-left: calc(20px + (16 * 1 * 1px)); + padding-left: calc(20px + calc(16 * 1 * 1px)); + padding-left: calc(var(--aa-icon-size) + var(--aa-spacing)); +} + +.aa-ItemContentBody { + display: grid; + gap: calc(((16 * 1 * 1px) / 2) / 2); + gap: calc(calc(calc(16 * 1 * 1px) / 2) / 2); + grid-gap: calc(((16 * 1 * 1px) / 2) / 2); + grid-gap: calc(calc(calc(16 * 1 * 1px) / 2) / 2); + grid-gap: calc(var(--aa-spacing-half) / 2); + gap: calc(var(--aa-spacing-half) / 2); +} + +.aa-ItemContentTitle { + display: inline-block; + margin: 0 0.5em 0 0; + max-width: 100%; + overflow: hidden; + padding: 0; + text-overflow: ellipsis; + white-space: nowrap; +} + +.aa-ItemContentSubtitle { + font-size: 0.92em; +} +.aa-ItemContentSubtitleIcon::before { + border-color: var(--bs-tertiary-color); + border-style: solid; + content: ""; + display: inline-block; + left: 1px; + position: relative; + top: -3px; +} + +.aa-ItemContentSubtitle--inline .aa-ItemContentSubtitleIcon::before { + border-width: 0 0 1.5px; + margin-left: calc((16 * 1 * 1px) / 2); + margin-left: calc(calc(16 * 1 * 1px) / 2); + margin-left: var(--aa-spacing-half); + margin-right: calc(((16 * 1 * 1px) / 2) / 2); + margin-right: calc(calc(calc(16 * 1 * 1px) / 2) / 2); + margin-right: calc(var(--aa-spacing-half) / 2); + width: calc(((16 * 1 * 1px) / 2) + 2px); + width: calc(calc(calc(16 * 1 * 1px) / 2) + 2px); + width: calc(var(--aa-spacing-half) + 2px); +} + +.aa-ItemContentSubtitle--standalone { + align-items: center; + color: var(--bs-body-color); + display: grid; + gap: calc((16 * 1 * 1px) / 2); + gap: calc(calc(16 * 1 * 1px) / 2); + grid-gap: calc((16 * 1 * 1px) / 2); + grid-gap: calc(calc(16 * 1 * 1px) / 2); + grid-gap: var(--aa-spacing-half); + gap: var(--aa-spacing-half); + grid-auto-flow: column; + justify-content: start; +} +.aa-ItemContentSubtitle--standalone .aa-ItemContentSubtitleIcon::before { + border-radius: 0 0 0 3px; + border-width: 0 0 1.5px 1.5px; + height: calc((16 * 1 * 1px) / 2); + height: calc(calc(16 * 1 * 1px) / 2); + height: var(--aa-spacing-half); + width: calc((16 * 1 * 1px) / 2); + width: calc(calc(16 * 1 * 1px) / 2); + width: var(--aa-spacing-half); +} + +.aa-ItemContentSubtitleCategory { + color: var(--bs-secondary-color); + font-weight: 500; +} + +.aa-ItemContentDescription { + color: var(--bs-body-color); + font-size: 0.85em; + max-width: 100%; + overflow-x: hidden; + text-overflow: ellipsis; +} +.aa-ItemContentDescription:empty { + display: none; +} +.aa-ItemContentDescription mark { + background: rgba(245, 223, 77, 0.5); + background: rgba(var(--aa-description-highlight-background-color-rgb), var(--aa-description-highlight-background-color-alpha)); + color: rgba(38, 38, 39, 1); + color: rgba(var(--aa-text-color-rgb), var(--aa-text-color-alpha)); + font-style: normal; + font-weight: 500; + font-weight: var(--aa-font-weight-medium); +} + +.aa-ItemContentDash { + color: var(--bs-secondary-color); + display: none; + opacity: 0.4; +} + +.aa-ItemContentTag { + color: rgba(var(--bs-primary-rgb), 1.0);; + border-radius: 3px; + margin: 0 0.4em 0 0; + padding: 0.08em 0.3em; +} + +.aa-ItemWrapper, +.aa-ItemLink { + align-items: center; + color: inherit; + display: grid; + gap: calc(((16 * 1 * 1px) / 2) / 2); + gap: calc(calc(calc(16 * 1 * 1px) / 2) / 2); + grid-gap: calc(((16 * 1 * 1px) / 2) / 2); + grid-gap: calc(calc(calc(16 * 1 * 1px) / 2) / 2); + grid-gap: calc(var(--aa-spacing-half) / 2); + gap: calc(var(--aa-spacing-half) / 2); + grid-auto-flow: column; + justify-content: space-between; + width: 100%; +} + +.aa-ItemLink { + color: inherit; + -webkit-text-decoration: none; + text-decoration: none; +} + +.aa-ItemActions { + display: grid; + grid-auto-flow: column; + height: 100%; + justify-self: end; + margin: 0 calc((16 * 1 * 1px) / -3); + margin: 0 calc(calc(16 * 1 * 1px) / -3); + margin: 0 calc(var(--aa-spacing) / -3); + padding: 0 2px 0 0; +} + +.aa-ItemActionButton { + align-items: center; + background: none; + border: 0; + color: var(--bs-secondary-color); + cursor: pointer; + display: flex; + flex-shrink: 0; + padding: 0; +} +.aa-ItemActionButton:hover svg, .aa-ItemActionButton:focus svg { + color: var(--bs-body-color); +} +@media (hover: none) and (pointer: coarse) { + .aa-ItemActionButton:hover svg, .aa-ItemActionButton:focus svg { + color: inherit; + } +} +.aa-ItemActionButton svg { + color: var(--bs-secondary-color); + margin: 0; + margin: calc(calc(16 * 1 * 1px) / 3); + margin: calc(var(--aa-spacing) / 3); + stroke-width: 1.6; + stroke-width: var(--aa-icon-stroke-width); + width: 20px; + width: var(--aa-action-icon-size); +} + +.aa-ActiveOnly { + visibility: hidden; +} + +/*----------------*/ +/* 7. Panel Header*/ +/*----------------*/ +.aa-PanelHeader { + align-items: center; + background: var(--bs-primary-bg-subtle); + color: #fff; + display: grid; + height: var(--aa-modal-header-height); + margin: 0; + padding: calc((16 * 1 * 1px) / 2) calc(16 * 1 * 1px); + padding: calc(calc(16 * 1 * 1px) / 2) calc(16 * 1 * 1px); + padding: var(--aa-spacing-half) var(--aa-spacing); + position: relative; +} +.aa-PanelHeader::after { + background-image: linear-gradient(rgba(255, 255, 255, 1), rgba(255, 255, 255, 0)); + background-image: linear-gradient(rgba(var(--aa-background-color-rgb), 1), rgba(var(--aa-background-color-rgb), 0)); + bottom: calc(((16 * 1 * 1px) / 2) * -1); + bottom: calc(calc(calc(16 * 1 * 1px) / 2) * -1); + bottom: calc(var(--aa-spacing-half) * -1); + content: ""; + height: calc((16 * 1 * 1px) / 2); + height: calc(calc(16 * 1 * 1px) / 2); + height: var(--aa-spacing-half); + left: 0; + pointer-events: none; + position: absolute; + right: 0; + z-index: 9999; + z-index: var(--aa-base-z-index); +} + +/*----------------*/ +/* 8. Panel Footer*/ +/*----------------*/ +.aa-PanelFooter { + background-color: var(--bs-body-bg); + box-shadow: inset 0 1px 0 var(--bs-dropdown-border-color); + display: flex; + justify-content: space-between; + margin: 0; + padding: calc(16 * 1 * 1px); + padding: var(--aa-spacing); + position: relative; + z-index: 9999; + z-index: var(--aa-base-z-index); +} +.aa-PanelFooter::after { + background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(128, 126, 163, 0.6)); + background-image: linear-gradient(rgba(var(--aa-background-color-rgb), 0), rgba(var(--aa-muted-color-rgb), var(--aa-muted-color-alpha))); + content: ""; + height: calc(16 * 1 * 1px); + height: var(--aa-spacing); + left: 0; + opacity: 0.12; + pointer-events: none; + position: absolute; + right: 0; + top: calc((16 * 1 * 1px) * -1); + top: calc(calc(16 * 1 * 1px) * -1); + top: calc(var(--aa-spacing) * -1); + z-index: calc(9999 - 1); + z-index: calc(var(--aa-base-z-index) - 1); +} + +/*----------------*/ +/* 9. Detached Mode*/ +/*----------------*/ +.aa-DetachedContainer { + background: var(--bs-body-bg); + bottom: 0; + box-shadow: 0 0 0 1px rgba(35, 38, 59, 0.1), + 0 6px 16px -4px rgba(35, 38, 59, 0.15); + box-shadow: var(--aa-panel-shadow); + display: flex; + flex-direction: column; + left: 0; + margin: 0; + overflow: hidden; + padding: 0; + position: fixed; + right: 0; + top: 0; + z-index: 9999; + z-index: var(--aa-base-z-index); +} +.aa-DetachedContainer::after { + height: 32px; +} +.aa-DetachedContainer .aa-SourceHeader { + margin: calc((16 * 1 * 1px) / 2) 0 calc((16 * 1 * 1px) / 2) 2px; + margin: calc(calc(16 * 1 * 1px) / 2) 0 calc(calc(16 * 1 * 1px) / 2) 2px; + margin: var(--aa-spacing-half) 0 var(--aa-spacing-half) 2px; +} +.aa-DetachedContainer .aa-Panel { + background-color: var(--bs-body-bg); + border-radius: 0; + box-shadow: none; + flex-grow: 1; + margin: 0; + padding: 0; + position: relative; +} +.aa-DetachedContainer .aa-PanelLayout { + bottom: 0; + box-shadow: none; + left: 0; + margin: 0; + max-height: none; + overflow-y: auto; + position: absolute; + right: 0; + top: 0; + width: 100%; +} +.aa-DetachedFormContainer { + border-bottom: solid 1px rgba(128, 126, 163, 0.3); + border-bottom: solid 1px rgba(var(--aa-panel-border-color-rgb), var(--aa-panel-border-color-alpha)); + display: flex; + flex-direction: row; + justify-content: space-between; + margin: 0; + padding: calc((16 * 1 * 1px) / 2); + padding: calc(calc(16 * 1 * 1px) / 2); + padding: var(--aa-spacing-half); +} +.aa-DetachedCancelButton { + background: none; + border: 0; + border-radius: 3px; + color: var(--bs-body-color); + cursor: pointer; + font: inherit; + margin: 0 0 0 calc((16 * 1 * 1px) / 2); + margin: 0 0 0 calc(calc(16 * 1 * 1px) / 2); + margin: 0 0 0 var(--aa-spacing-half); + padding: 0 calc((16 * 1 * 1px) / 2); + padding: 0 calc(calc(16 * 1 * 1px) / 2); + padding: 0 var(--aa-spacing-half); +} +.aa-DetachedCancelButton:hover, .aa-DetachedCancelButton:focus { + box-shadow: inset 0 0 0 1px rgba(128, 126, 163, 0.3); + box-shadow: inset 0 0 0 1px rgba(var(--aa-panel-border-color-rgb), var(--aa-panel-border-color-alpha)); +} + +.aa-DetachedContainer--modal { + border-radius: 6px; + bottom: inherit; + height: auto; + margin: 0 auto; + max-width: 680px; + max-width: var(--aa-detached-modal-max-width); + position: absolute; + top: 3%; +} +.aa-DetachedContainer--modal .aa-PanelLayout { + max-height: 500px; + max-height: var(--aa-detached-modal-max-height); + padding-bottom: calc((16 * 1 * 1px) / 2); + padding-bottom: calc(calc(16 * 1 * 1px) / 2); + padding-bottom: var(--aa-spacing-half); + position: static; +} +.aa-DetachedContainer--modal .aa-PanelLayout:empty { + display: none; +} + +/* Search Button*/ +.aa-DetachedSearchButton { + align-items: center; + background-color: var(--bs-body-bg); + border: 1px solid var(--bs-secondary-border-subtle); + border-radius: 3px; + color: var(--bs-secondary-color); + cursor: pointer; + display: flex; + font: inherit; + font-family: inherit; + font-family: var(--aa-font-family); + font-size: calc(16 * 1px); + font-size: var(--aa-font-size); + height: 44px; + height: var(--aa-search-input-height); + margin: 0; + padding: 0 calc(44px / 8); + padding: 0 calc(var(--aa-search-input-height) / 8); + position: relative; + text-align: left; + width: 100%; +} +.aa-DetachedSearchButton:focus { + border-color: var(--bs-primary-border-subtle); + box-shadow: rgba(62, 52, 211, 0.2) 0 0 0 3px, inset rgba(62, 52, 211, 0.2) 0 0 0 2px; + box-shadow: var(--bs-primary-border-subtle) 0 0 0 3px, inset var(--bs-primary-border-subtle) 0 0 0 2px; + outline: currentColor none medium; +} +.aa-DetachedSearchButtonIcon { + align-items: center; + color: rgba(var(--bs-primary-rgb), 1.0); + cursor: auto; + cursor: initial; + display: flex; + flex-shrink: 0; + height: 100%; + justify-content: center; + width: calc(20px + (16 * 1 * 1px)); + width: calc(20px + calc(16 * 1 * 1px)); + width: calc(var(--aa-icon-size) + var(--aa-spacing)); +} + +.aa-DetachedSearchButtonQuery { + color: var(--bs-body-color); + line-height: 1.25em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.aa-DetachedSearchButtonPlaceholder[hidden] { + display: none; +} + +/* Remove scroll on `body`*/ +.aa-Detached { + height: 100vh; + overflow: hidden; +} + +.aa-DetachedOverlay { + background-color: rgba(115, 114, 129, 0.4); + background-color: rgba(var(--aa-overlay-color-rgb), var(--aa-overlay-color-alpha)); + height: 100vh; + left: 0; + margin: 0; + padding: 0; + position: fixed; + right: 0; + top: 0; + z-index: calc(9999 - 1); + z-index: calc(var(--aa-base-z-index) - 1); +} + +/*----------------*/ +/* 10. Gradients*/ +/*----------------*/ +.aa-GradientTop, +.aa-GradientBottom { + height: calc((16 * 1 * 1px) / 2); + height: calc(calc(16 * 1 * 1px) / 2); + height: var(--aa-spacing-half); + left: 0; + pointer-events: none; + position: absolute; + right: 0; + z-index: 9999; + z-index: var(--aa-base-z-index); +} + +.aa-GradientTop { + background-image: linear-gradient(rgba(255, 255, 255, 1), rgba(255, 255, 255, 0)); + background-image: linear-gradient(rgba(var(--aa-background-color-rgb), 1), rgba(var(--aa-background-color-rgb), 0)); + top: 0; +} + +.aa-GradientBottom { + background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 1)); + background-image: linear-gradient(rgba(var(--aa-background-color-rgb), 0), rgba(var(--aa-background-color-rgb), 1)); + border-bottom-left-radius: calc((16 * 1 * 1px) / 4); + border-bottom-left-radius: calc(calc(16 * 1 * 1px) / 4); + border-bottom-left-radius: calc(var(--aa-spacing) / 4); + border-bottom-right-radius: calc((16 * 1 * 1px) / 4); + border-bottom-right-radius: calc(calc(16 * 1 * 1px) / 4); + border-bottom-right-radius: calc(var(--aa-spacing) / 4); + bottom: 0; +} + +/*----------------*/ +/* 11. Utilities*/ +/*----------------*/ +@media (hover: none) and (pointer: coarse) { + .aa-DesktopOnly { + display: none; + } +} + +@media (hover: hover) { + .aa-TouchOnly { + display: none; + } +} \ No newline at end of file diff --git a/assets/css/components/ckeditor.css b/assets/css/components/ckeditor.css index f8a682a2..5f093bf2 100644 --- a/assets/css/components/ckeditor.css +++ b/assets/css/components/ckeditor.css @@ -24,9 +24,8 @@ /** Should be the same settings, as in label_style.css */ .ck-html-label .ck-content { font-family: "DejaVu Sans Mono", monospace; - font-size: 12px; + font-size: 12pt; line-height: 1.0; - font-size-adjust: 1.5; } .ck-html-label .ck-content p { @@ -72,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/datatables_select_bs5.css b/assets/css/components/datatables_select_bs5.css new file mode 100644 index 00000000..7c717bf4 --- /dev/null +++ b/assets/css/components/datatables_select_bs5.css @@ -0,0 +1,69 @@ +/****************************************************************************************** +* This styles the checkboxes of the select extension exactly like the ones in bootstrap 5 +******************************************************************************************/ + +table.dataTable > tbody > tr > .selected { + background-color: var(--bs-primary-bg-subtle) !important; + color: white; +} +table.dataTable > tbody > tr > .dt-select { + text-align: center; + vertical-align: middle; +} +table.dataTable > thead > tr > .dt-select { + text-align: center; +} +table.dataTable input.dt-select-checkbox { + --bs-form-check-bg: var(--bs-body-bg); + flex-shrink: 0; + width: 1em; + height: 1em; + margin-top: 0.25em; + vertical-align: top; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-form-check-bg); + background-image: var(--bs-form-check-bg-image); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + border: var(--bs-border-width) solid var(--bs-border-color); + -webkit-print-color-adjust: exact; + color-adjust: exact; + print-color-adjust: exact; + border-radius: 0.25em; +} + +table.dataTable input.dt-select-checkbox:checked { + background-color: rgb(var(--bs-secondary-rgb)); + border-color: rgb(var(--bs-secondary-rgb)); + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e"); +} + +table.dataTable input.dt-select-checkbox:indeterminate { + background-color: rgb(var(--bs-secondary-rgb)); + border-color: rgb(var(--bs-secondary-rgb)); + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e"); +} + + + + +div.dt-container span.select-info, +div.dt-container span.select-item { + margin-left: 0.5em; +} + + +@media screen and (max-width: 640px) { + div.dt-container span.select-info, + div.dt-container span.select-item { + margin-left: 0; + display: block; + } +} +table.dataTable.table-sm tbody td.select-checkbox::before { + margin-top: -9px; +} + diff --git a/assets/css/components/toggle_password.css b/assets/css/components/toggle_password.css new file mode 100644 index 00000000..f1f4a889 --- /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/css/email/foundation-emails.css b/assets/css/email/foundation-emails.css index 723728d3..2b62bc09 100644 --- a/assets/css/email/foundation-emails.css +++ b/assets/css/email/foundation-emails.css @@ -1,594 +1,708 @@ -/* - * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). +/** + * Copyright (c) 2017 ZURB, inc. * - * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics) + * MIT License * - * 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. + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following conditions: * - * 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. + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + * Software. * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ .wrapper { - width: 100%; } + width: 100%; +} #outlook a { - padding: 0; } + padding: 0; +} body { - width: 100% !important; - min-width: 100%; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; - margin: 0; - Margin: 0; - padding: 0; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - box-sizing: border-box; } + width: 100% !important; + min-width: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + margin: 0; + Margin: 0; + padding: 0; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} .ExternalClass { - width: 100%; } - .ExternalClass, - .ExternalClass p, - .ExternalClass span, - .ExternalClass font, - .ExternalClass td, - .ExternalClass div { - line-height: 100%; } + width: 100%; +} + +.ExternalClass, +.ExternalClass p, +.ExternalClass span, +.ExternalClass font, +.ExternalClass td, +.ExternalClass div { + line-height: 100%; +} #backgroundTable { - margin: 0; - Margin: 0; - padding: 0; - width: 100% !important; - line-height: 100% !important; } + margin: 0; + Margin: 0; + padding: 0; + width: 100% !important; + line-height: 100% !important; +} img { - outline: none; - text-decoration: none; - -ms-interpolation-mode: bicubic; - width: auto; - max-width: 100%; - clear: both; - display: block; } + outline: none; + text-decoration: none; + -ms-interpolation-mode: bicubic; + width: auto; + max-width: 100%; + clear: both; + display: block; +} center { - width: 100%; - min-width: 580px; } + width: 100%; + min-width: 580px; +} a img { - border: none; } + border: none; +} p { - margin: 0 0 0 10px; - Margin: 0 0 0 10px; } + margin: 0 0 0 10px; + Margin: 0 0 0 10px; +} table { - border-spacing: 0; - border-collapse: collapse; } + border-spacing: 0; + border-collapse: collapse; +} td { - word-wrap: break-word; - -webkit-hyphens: auto; - -moz-hyphens: auto; - hyphens: auto; - border-collapse: collapse !important; } + word-wrap: break-word; + -webkit-hyphens: auto; + -moz-hyphens: auto; + hyphens: auto; + border-collapse: collapse !important; +} table, tr, td { - padding: 0; - vertical-align: top; - text-align: left; } + padding: 0; + vertical-align: top; + text-align: left; +} @media only screen { - html { - min-height: 100%; - background: #f3f3f3; } } + html { + min-height: 100%; + background: #f3f3f3; + } +} table.body { - background: #f3f3f3; - height: 100%; - width: 100%; } + background: #f3f3f3; + height: 100%; + width: 100%; +} table.container { - background: #fefefe; - width: 580px; - margin: 0 auto; - Margin: 0 auto; - text-align: inherit; } + background: #fefefe; + width: 580px; + margin: 0 auto; + Margin: 0 auto; + text-align: inherit; +} table.row { - padding: 0; - width: 100%; - position: relative; } + padding: 0; + width: 100%; + position: relative; +} table.spacer { - width: 100%; } - table.spacer td { - mso-line-height-rule: exactly; } + width: 100%; +} + +table.spacer td { + mso-line-height-rule: exactly; +} table.container table.row { - display: table; } + display: table; +} td.columns, td.column, th.columns, th.column { - margin: 0 auto; - Margin: 0 auto; - padding-left: 16px; - padding-bottom: 16px; } - td.columns .column, - td.columns .columns, - td.column .column, - td.column .columns, - th.columns .column, - th.columns .columns, - th.column .column, - th.column .columns { + margin: 0 auto; + Margin: 0 auto; + padding-left: 16px; + padding-bottom: 16px; +} + +td.columns .column, +td.columns .columns, +td.column .column, +td.column .columns, +th.columns .column, +th.columns .columns, +th.column .column, +th.column .columns { padding-left: 0 !important; - padding-right: 0 !important; } - td.columns .column center, - td.columns .columns center, - td.column .column center, - td.column .columns center, - th.columns .column center, - th.columns .columns center, - th.column .column center, - th.column .columns center { - min-width: none !important; } + padding-right: 0 !important; +} + +td.columns .column center, +td.columns .columns center, +td.column .column center, +td.column .columns center, +th.columns .column center, +th.columns .columns center, +th.column .column center, +th.column .columns center { + min-width: none !important; +} td.columns.last, td.column.last, th.columns.last, th.column.last { - padding-right: 16px; } + padding-right: 16px; +} td.columns table:not(.button), td.column table:not(.button), th.columns table:not(.button), th.column table:not(.button) { - width: 100%; } + width: 100%; +} td.large-1, th.large-1 { - width: 32.33333px; - padding-left: 8px; - padding-right: 8px; } + width: 32.33333px; + padding-left: 8px; + padding-right: 8px; +} td.large-1.first, th.large-1.first { - padding-left: 16px; } + padding-left: 16px; +} td.large-1.last, th.large-1.last { - padding-right: 16px; } + padding-right: 16px; +} .collapse > tbody > tr > td.large-1, .collapse > tbody > tr > th.large-1 { - padding-right: 0; - padding-left: 0; - width: 48.33333px; } + padding-right: 0; + padding-left: 0; + width: 48.33333px; +} .collapse td.large-1.first, .collapse th.large-1.first, .collapse td.large-1.last, .collapse th.large-1.last { - width: 56.33333px; } + width: 56.33333px; +} td.large-1 center, th.large-1 center { - min-width: 0.33333px; } + min-width: 0.33333px; +} .body .columns td.large-1, .body .column td.large-1, .body .columns th.large-1, .body .column th.large-1 { - width: 8.33333%; } + width: 8.33333%; +} td.large-2, th.large-2 { - width: 80.66667px; - padding-left: 8px; - padding-right: 8px; } + width: 80.66667px; + padding-left: 8px; + padding-right: 8px; +} td.large-2.first, th.large-2.first { - padding-left: 16px; } + padding-left: 16px; +} td.large-2.last, th.large-2.last { - padding-right: 16px; } + padding-right: 16px; +} .collapse > tbody > tr > td.large-2, .collapse > tbody > tr > th.large-2 { - padding-right: 0; - padding-left: 0; - width: 96.66667px; } + padding-right: 0; + padding-left: 0; + width: 96.66667px; +} .collapse td.large-2.first, .collapse th.large-2.first, .collapse td.large-2.last, .collapse th.large-2.last { - width: 104.66667px; } + width: 104.66667px; +} td.large-2 center, th.large-2 center { - min-width: 48.66667px; } + min-width: 48.66667px; +} .body .columns td.large-2, .body .column td.large-2, .body .columns th.large-2, .body .column th.large-2 { - width: 16.66667%; } + width: 16.66667%; +} td.large-3, th.large-3 { - width: 129px; - padding-left: 8px; - padding-right: 8px; } + width: 129px; + padding-left: 8px; + padding-right: 8px; +} td.large-3.first, th.large-3.first { - padding-left: 16px; } + padding-left: 16px; +} td.large-3.last, th.large-3.last { - padding-right: 16px; } + padding-right: 16px; +} .collapse > tbody > tr > td.large-3, .collapse > tbody > tr > th.large-3 { - padding-right: 0; - padding-left: 0; - width: 145px; } + padding-right: 0; + padding-left: 0; + width: 145px; +} .collapse td.large-3.first, .collapse th.large-3.first, .collapse td.large-3.last, .collapse th.large-3.last { - width: 153px; } + width: 153px; +} td.large-3 center, th.large-3 center { - min-width: 97px; } + min-width: 97px; +} .body .columns td.large-3, .body .column td.large-3, .body .columns th.large-3, .body .column th.large-3 { - width: 25%; } + width: 25%; +} td.large-4, th.large-4 { - width: 177.33333px; - padding-left: 8px; - padding-right: 8px; } + width: 177.33333px; + padding-left: 8px; + padding-right: 8px; +} td.large-4.first, th.large-4.first { - padding-left: 16px; } + padding-left: 16px; +} td.large-4.last, th.large-4.last { - padding-right: 16px; } + padding-right: 16px; +} .collapse > tbody > tr > td.large-4, .collapse > tbody > tr > th.large-4 { - padding-right: 0; - padding-left: 0; - width: 193.33333px; } + padding-right: 0; + padding-left: 0; + width: 193.33333px; +} .collapse td.large-4.first, .collapse th.large-4.first, .collapse td.large-4.last, .collapse th.large-4.last { - width: 201.33333px; } + width: 201.33333px; +} td.large-4 center, th.large-4 center { - min-width: 145.33333px; } + min-width: 145.33333px; +} .body .columns td.large-4, .body .column td.large-4, .body .columns th.large-4, .body .column th.large-4 { - width: 33.33333%; } + width: 33.33333%; +} td.large-5, th.large-5 { - width: 225.66667px; - padding-left: 8px; - padding-right: 8px; } + width: 225.66667px; + padding-left: 8px; + padding-right: 8px; +} td.large-5.first, th.large-5.first { - padding-left: 16px; } + padding-left: 16px; +} td.large-5.last, th.large-5.last { - padding-right: 16px; } + padding-right: 16px; +} .collapse > tbody > tr > td.large-5, .collapse > tbody > tr > th.large-5 { - padding-right: 0; - padding-left: 0; - width: 241.66667px; } + padding-right: 0; + padding-left: 0; + width: 241.66667px; +} .collapse td.large-5.first, .collapse th.large-5.first, .collapse td.large-5.last, .collapse th.large-5.last { - width: 249.66667px; } + width: 249.66667px; +} td.large-5 center, th.large-5 center { - min-width: 193.66667px; } + min-width: 193.66667px; +} .body .columns td.large-5, .body .column td.large-5, .body .columns th.large-5, .body .column th.large-5 { - width: 41.66667%; } + width: 41.66667%; +} td.large-6, th.large-6 { - width: 274px; - padding-left: 8px; - padding-right: 8px; } + width: 274px; + padding-left: 8px; + padding-right: 8px; +} td.large-6.first, th.large-6.first { - padding-left: 16px; } + padding-left: 16px; +} td.large-6.last, th.large-6.last { - padding-right: 16px; } + padding-right: 16px; +} .collapse > tbody > tr > td.large-6, .collapse > tbody > tr > th.large-6 { - padding-right: 0; - padding-left: 0; - width: 290px; } + padding-right: 0; + padding-left: 0; + width: 290px; +} .collapse td.large-6.first, .collapse th.large-6.first, .collapse td.large-6.last, .collapse th.large-6.last { - width: 298px; } + width: 298px; +} td.large-6 center, th.large-6 center { - min-width: 242px; } + min-width: 242px; +} .body .columns td.large-6, .body .column td.large-6, .body .columns th.large-6, .body .column th.large-6 { - width: 50%; } + width: 50%; +} td.large-7, th.large-7 { - width: 322.33333px; - padding-left: 8px; - padding-right: 8px; } + width: 322.33333px; + padding-left: 8px; + padding-right: 8px; +} td.large-7.first, th.large-7.first { - padding-left: 16px; } + padding-left: 16px; +} td.large-7.last, th.large-7.last { - padding-right: 16px; } + padding-right: 16px; +} .collapse > tbody > tr > td.large-7, .collapse > tbody > tr > th.large-7 { - padding-right: 0; - padding-left: 0; - width: 338.33333px; } + padding-right: 0; + padding-left: 0; + width: 338.33333px; +} .collapse td.large-7.first, .collapse th.large-7.first, .collapse td.large-7.last, .collapse th.large-7.last { - width: 346.33333px; } + width: 346.33333px; +} td.large-7 center, th.large-7 center { - min-width: 290.33333px; } + min-width: 290.33333px; +} .body .columns td.large-7, .body .column td.large-7, .body .columns th.large-7, .body .column th.large-7 { - width: 58.33333%; } + width: 58.33333%; +} td.large-8, th.large-8 { - width: 370.66667px; - padding-left: 8px; - padding-right: 8px; } + width: 370.66667px; + padding-left: 8px; + padding-right: 8px; +} td.large-8.first, th.large-8.first { - padding-left: 16px; } + padding-left: 16px; +} td.large-8.last, th.large-8.last { - padding-right: 16px; } + padding-right: 16px; +} .collapse > tbody > tr > td.large-8, .collapse > tbody > tr > th.large-8 { - padding-right: 0; - padding-left: 0; - width: 386.66667px; } + padding-right: 0; + padding-left: 0; + width: 386.66667px; +} .collapse td.large-8.first, .collapse th.large-8.first, .collapse td.large-8.last, .collapse th.large-8.last { - width: 394.66667px; } + width: 394.66667px; +} td.large-8 center, th.large-8 center { - min-width: 338.66667px; } + min-width: 338.66667px; +} .body .columns td.large-8, .body .column td.large-8, .body .columns th.large-8, .body .column th.large-8 { - width: 66.66667%; } + width: 66.66667%; +} td.large-9, th.large-9 { - width: 419px; - padding-left: 8px; - padding-right: 8px; } + width: 419px; + padding-left: 8px; + padding-right: 8px; +} td.large-9.first, th.large-9.first { - padding-left: 16px; } + padding-left: 16px; +} td.large-9.last, th.large-9.last { - padding-right: 16px; } + padding-right: 16px; +} .collapse > tbody > tr > td.large-9, .collapse > tbody > tr > th.large-9 { - padding-right: 0; - padding-left: 0; - width: 435px; } + padding-right: 0; + padding-left: 0; + width: 435px; +} .collapse td.large-9.first, .collapse th.large-9.first, .collapse td.large-9.last, .collapse th.large-9.last { - width: 443px; } + width: 443px; +} td.large-9 center, th.large-9 center { - min-width: 387px; } + min-width: 387px; +} .body .columns td.large-9, .body .column td.large-9, .body .columns th.large-9, .body .column th.large-9 { - width: 75%; } + width: 75%; +} td.large-10, th.large-10 { - width: 467.33333px; - padding-left: 8px; - padding-right: 8px; } + width: 467.33333px; + padding-left: 8px; + padding-right: 8px; +} td.large-10.first, th.large-10.first { - padding-left: 16px; } + padding-left: 16px; +} td.large-10.last, th.large-10.last { - padding-right: 16px; } + padding-right: 16px; +} .collapse > tbody > tr > td.large-10, .collapse > tbody > tr > th.large-10 { - padding-right: 0; - padding-left: 0; - width: 483.33333px; } + padding-right: 0; + padding-left: 0; + width: 483.33333px; +} .collapse td.large-10.first, .collapse th.large-10.first, .collapse td.large-10.last, .collapse th.large-10.last { - width: 491.33333px; } + width: 491.33333px; +} td.large-10 center, th.large-10 center { - min-width: 435.33333px; } + min-width: 435.33333px; +} .body .columns td.large-10, .body .column td.large-10, .body .columns th.large-10, .body .column th.large-10 { - width: 83.33333%; } + width: 83.33333%; +} td.large-11, th.large-11 { - width: 515.66667px; - padding-left: 8px; - padding-right: 8px; } + width: 515.66667px; + padding-left: 8px; + padding-right: 8px; +} td.large-11.first, th.large-11.first { - padding-left: 16px; } + padding-left: 16px; +} td.large-11.last, th.large-11.last { - padding-right: 16px; } + padding-right: 16px; +} .collapse > tbody > tr > td.large-11, .collapse > tbody > tr > th.large-11 { - padding-right: 0; - padding-left: 0; - width: 531.66667px; } + padding-right: 0; + padding-left: 0; + width: 531.66667px; +} .collapse td.large-11.first, .collapse th.large-11.first, .collapse td.large-11.last, .collapse th.large-11.last { - width: 539.66667px; } + width: 539.66667px; +} td.large-11 center, th.large-11 center { - min-width: 483.66667px; } + min-width: 483.66667px; +} .body .columns td.large-11, .body .column td.large-11, .body .columns th.large-11, .body .column th.large-11 { - width: 91.66667%; } + width: 91.66667%; +} td.large-12, th.large-12 { - width: 564px; - padding-left: 8px; - padding-right: 8px; } + width: 564px; + padding-left: 8px; + padding-right: 8px; +} td.large-12.first, th.large-12.first { - padding-left: 16px; } + padding-left: 16px; +} td.large-12.last, th.large-12.last { - padding-right: 16px; } + padding-right: 16px; +} .collapse > tbody > tr > td.large-12, .collapse > tbody > tr > th.large-12 { - padding-right: 0; - padding-left: 0; - width: 580px; } + padding-right: 0; + padding-left: 0; + width: 580px; +} .collapse td.large-12.first, .collapse th.large-12.first, .collapse td.large-12.last, .collapse th.large-12.last { - width: 588px; } + width: 588px; +} td.large-12 center, th.large-12 center { - min-width: 532px; } + min-width: 532px; +} .body .columns td.large-12, .body .column td.large-12, .body .columns th.large-12, .body .column th.large-12 { - width: 100%; } + width: 100%; +} td.large-offset-1, td.large-offset-1.first, @@ -596,7 +710,8 @@ td.large-offset-1.last, th.large-offset-1, th.large-offset-1.first, th.large-offset-1.last { - padding-left: 64.33333px; } + padding-left: 64.33333px; +} td.large-offset-2, td.large-offset-2.first, @@ -604,7 +719,8 @@ td.large-offset-2.last, th.large-offset-2, th.large-offset-2.first, th.large-offset-2.last { - padding-left: 112.66667px; } + padding-left: 112.66667px; +} td.large-offset-3, td.large-offset-3.first, @@ -612,7 +728,8 @@ td.large-offset-3.last, th.large-offset-3, th.large-offset-3.first, th.large-offset-3.last { - padding-left: 161px; } + padding-left: 161px; +} td.large-offset-4, td.large-offset-4.first, @@ -620,7 +737,8 @@ td.large-offset-4.last, th.large-offset-4, th.large-offset-4.first, th.large-offset-4.last { - padding-left: 209.33333px; } + padding-left: 209.33333px; +} td.large-offset-5, td.large-offset-5.first, @@ -628,7 +746,8 @@ td.large-offset-5.last, th.large-offset-5, th.large-offset-5.first, th.large-offset-5.last { - padding-left: 257.66667px; } + padding-left: 257.66667px; +} td.large-offset-6, td.large-offset-6.first, @@ -636,7 +755,8 @@ td.large-offset-6.last, th.large-offset-6, th.large-offset-6.first, th.large-offset-6.last { - padding-left: 306px; } + padding-left: 306px; +} td.large-offset-7, td.large-offset-7.first, @@ -644,7 +764,8 @@ td.large-offset-7.last, th.large-offset-7, th.large-offset-7.first, th.large-offset-7.last { - padding-left: 354.33333px; } + padding-left: 354.33333px; +} td.large-offset-8, td.large-offset-8.first, @@ -652,7 +773,8 @@ td.large-offset-8.last, th.large-offset-8, th.large-offset-8.first, th.large-offset-8.last { - padding-left: 402.66667px; } + padding-left: 402.66667px; +} td.large-offset-9, td.large-offset-9.first, @@ -660,7 +782,8 @@ td.large-offset-9.last, th.large-offset-9, th.large-offset-9.first, th.large-offset-9.last { - padding-left: 451px; } + padding-left: 451px; +} td.large-offset-10, td.large-offset-10.first, @@ -668,7 +791,8 @@ td.large-offset-10.last, th.large-offset-10, th.large-offset-10.first, th.large-offset-10.last { - padding-left: 499.33333px; } + padding-left: 499.33333px; +} td.large-offset-11, td.large-offset-11.first, @@ -676,45 +800,58 @@ td.large-offset-11.last, th.large-offset-11, th.large-offset-11.first, th.large-offset-11.last { - padding-left: 547.66667px; } + padding-left: 547.66667px; +} td.expander, th.expander { - visibility: hidden; - width: 0; - padding: 0 !important; } + visibility: hidden; + width: 0; + padding: 0 !important; +} table.container.radius { - border-radius: 0; - border-collapse: separate; } + border-radius: 0; + border-collapse: separate; +} .block-grid { - width: 100%; - max-width: 580px; } - .block-grid td { + width: 100%; + max-width: 580px; +} + +.block-grid td { display: inline-block; - padding: 8px; } + padding: 8px; +} .up-2 td { - width: 274px !important; } + width: 274px !important; +} .up-3 td { - width: 177px !important; } + width: 177px !important; +} .up-4 td { - width: 129px !important; } + width: 129px !important; +} .up-5 td { - width: 100px !important; } + width: 100px !important; +} .up-6 td { - width: 80px !important; } + width: 80px !important; +} .up-7 td { - width: 66px !important; } + width: 66px !important; +} .up-8 td { - width: 56px !important; } + width: 56px !important; +} table.text-center, th.text-center, @@ -727,7 +864,8 @@ h5.text-center, h6.text-center, p.text-center, span.text-center { - text-align: center; } + text-align: center; +} table.text-left, th.text-left, @@ -740,7 +878,8 @@ h5.text-left, h6.text-left, p.text-left, span.text-left { - text-align: left; } + text-align: left; +} table.text-right, th.text-right, @@ -753,85 +892,110 @@ h5.text-right, h6.text-right, p.text-right, span.text-right { - text-align: right; } + text-align: right; +} span.text-center { - display: block; - width: 100%; - text-align: center; } + display: block; + width: 100%; + text-align: center; +} @media only screen and (max-width: 596px) { - .small-float-center { - margin: 0 auto !important; - float: none !important; - text-align: center !important; } - .small-text-center { - text-align: center !important; } - .small-text-left { - text-align: left !important; } - .small-text-right { - text-align: right !important; } } + .small-float-center { + margin: 0 auto !important; + float: none !important; + text-align: center !important; + } + + .small-text-center { + text-align: center !important; + } + + .small-text-left { + text-align: left !important; + } + + .small-text-right { + text-align: right !important; + } +} img.float-left { - float: left; - text-align: left; } + float: left; + text-align: left; +} img.float-right { - float: right; - text-align: right; } + float: right; + text-align: right; +} img.float-center, img.text-center { - margin: 0 auto; - Margin: 0 auto; - float: none; - text-align: center; } + margin: 0 auto; + Margin: 0 auto; + float: none; + text-align: center; +} table.float-center, td.float-center, th.float-center { - margin: 0 auto; - Margin: 0 auto; - float: none; - text-align: center; } + margin: 0 auto; + Margin: 0 auto; + float: none; + text-align: center; +} .hide-for-large { - display: none !important; - mso-hide: all; - overflow: hidden; - max-height: 0; - font-size: 0; - width: 0; - line-height: 0; } - @media only screen and (max-width: 596px) { + display: none !important; + mso-hide: all; + overflow: hidden; + max-height: 0; + font-size: 0; + width: 0; + line-height: 0; +} + +@media only screen and (max-width: 596px) { .hide-for-large { - display: block !important; - width: auto !important; - overflow: visible !important; - max-height: none !important; - font-size: inherit !important; - line-height: inherit !important; } } + display: block !important; + width: auto !important; + overflow: visible !important; + max-height: none !important; + font-size: inherit !important; + line-height: inherit !important; + } +} table.body table.container .hide-for-large * { - mso-hide: all; } - -@media only screen and (max-width: 596px) { - table.body table.container .hide-for-large, - table.body table.container .row.hide-for-large { - display: table !important; - width: 100% !important; } } - -@media only screen and (max-width: 596px) { - table.body table.container .callout-inner.hide-for-large { - display: table-cell !important; - width: 100% !important; } } - -@media only screen and (max-width: 596px) { - table.body table.container .show-for-large { - display: none !important; - width: 0; mso-hide: all; - overflow: hidden; } } +} + +@media only screen and (max-width: 596px) { + table.body table.container .hide-for-large, + table.body table.container .row.hide-for-large { + display: table !important; + width: 100% !important; + } +} + +@media only screen and (max-width: 596px) { + table.body table.container .callout-inner.hide-for-large { + display: table-cell !important; + width: 100% !important; + } +} + +@media only screen and (max-width: 596px) { + table.body table.container .show-for-large { + display: none !important; + width: 0; + mso-hide: all; + overflow: hidden; + } +} body, table.body, @@ -845,14 +1009,15 @@ p, td, th, a { - color: #0a0a0a; - font-family: Helvetica, Arial, sans-serif; - font-weight: normal; - padding: 0; - margin: 0; - Margin: 0; - text-align: left; - line-height: 1.3; } + color: #0a0a0a; + font-family: Helvetica, Arial, sans-serif; + font-weight: normal; + padding: 0; + margin: 0; + Margin: 0; + text-align: left; + line-height: 1.3; +} h1, h2, @@ -860,67 +1025,88 @@ h3, h4, h5, h6 { - color: inherit; - word-wrap: normal; - font-family: Helvetica, Arial, sans-serif; - font-weight: normal; - margin-bottom: 10px; - Margin-bottom: 10px; } + color: inherit; + word-wrap: normal; + font-family: Helvetica, Arial, sans-serif; + font-weight: normal; + margin-bottom: 10px; + Margin-bottom: 10px; +} h1 { - font-size: 34px; } + font-size: 34px; +} h2 { - font-size: 30px; } + font-size: 30px; +} h3 { - font-size: 28px; } + font-size: 28px; +} h4 { - font-size: 24px; } + font-size: 24px; +} h5 { - font-size: 20px; } + font-size: 20px; +} h6 { - font-size: 18px; } + font-size: 18px; +} body, table.body, p, td, th { - font-size: 16px; - line-height: 1.3; } + font-size: 16px; + line-height: 1.3; +} p { - margin-bottom: 10px; - Margin-bottom: 10px; } - p.lead { + margin-bottom: 10px; + Margin-bottom: 10px; +} + +p.lead { font-size: 20px; - line-height: 1.6; } - p.subheader { + line-height: 1.6; +} + +p.subheader { margin-top: 4px; margin-bottom: 8px; Margin-top: 4px; Margin-bottom: 8px; font-weight: normal; line-height: 1.4; - color: #8a8a8a; } + color: #8a8a8a; +} small { - font-size: 80%; - color: #cacaca; } + font-size: 80%; + color: #cacaca; +} a { - color: #2199e8; - text-decoration: none; } - a:hover { - color: #147dc2; } - a:active { - color: #147dc2; } - a:visited { - color: #2199e8; } + color: #2199e8; + text-decoration: none; +} + +a:hover { + color: #147dc2; +} + +a:active { + color: #147dc2; +} + +a:visited { + color: #2199e8; +} h1 a, h1 a:visited, @@ -934,24 +1120,34 @@ h5 a, h5 a:visited, h6 a, h6 a:visited { - color: #2199e8; } + color: #2199e8; +} pre { - background: #f3f3f3; - margin: 30px 0; - Margin: 30px 0; } - pre code { - color: #cacaca; } - pre code span.callout { - color: #8a8a8a; - font-weight: bold; } - pre code span.callout-strong { - color: #ff6908; - font-weight: bold; } + background: #f3f3f3; + margin: 30px 0; + Margin: 30px 0; +} + +pre code { + color: #cacaca; +} + +pre code span.callout { + color: #8a8a8a; + font-weight: bold; +} + +pre code span.callout-strong { + color: #ff6908; + font-weight: bold; +} table.hr { - width: 100%; } - table.hr th { + width: 100%; +} + +table.hr th { height: 0; max-width: 580px; border-top: 0; @@ -960,52 +1156,66 @@ table.hr { border-left: 0; margin: 20px auto; Margin: 20px auto; - clear: both; } + clear: both; +} .stat { - font-size: 40px; - line-height: 1; } - p + .stat { + font-size: 40px; + line-height: 1; +} + +p + .stat { margin-top: -16px; - Margin-top: -16px; } + Margin-top: -16px; +} span.preheader { - display: none !important; - visibility: hidden; - mso-hide: all !important; - font-size: 1px; - color: #f3f3f3; - line-height: 1px; - max-height: 0px; - max-width: 0px; - opacity: 0; - overflow: hidden; } + display: none !important; + visibility: hidden; + mso-hide: all !important; + font-size: 1px; + color: #f3f3f3; + line-height: 1px; + max-height: 0px; + max-width: 0px; + opacity: 0; + overflow: hidden; +} table.button { - width: auto; - margin: 0 0 16px 0; - Margin: 0 0 16px 0; } - table.button table td { + width: auto; + margin: 0 0 16px 0; + Margin: 0 0 16px 0; +} + +table.button table td { text-align: left; color: #fefefe; background: #2199e8; - border: 2px solid #2199e8; } - table.button table td a { - font-family: Helvetica, Arial, sans-serif; - font-size: 16px; - font-weight: bold; - color: #fefefe; - text-decoration: none; - display: inline-block; - padding: 8px 16px 8px 16px; - border: 0 solid #2199e8; - border-radius: 3px; } - table.button.radius table td { + border: 2px solid #2199e8; +} + +table.button table td a { + font-family: Helvetica, Arial, sans-serif; + font-size: 16px; + font-weight: bold; + color: #fefefe; + text-decoration: none; + display: inline-block; + padding: 8px 16px 8px 16px; + border: 0 solid #2199e8; border-radius: 3px; - border: none; } - table.button.rounded table td { +} + +table.button.radius table td { + border-radius: 3px; + border: none; +} + +table.button.rounded table td { border-radius: 500px; - border: none; } + border: none; +} table.button:hover table tr td a, table.button:active table tr td a, @@ -1019,349 +1229,491 @@ table.button.small table tr td a:visited, table.button.large:hover table tr td a, table.button.large:active table tr td a, table.button.large table tr td a:visited { - color: #fefefe; } + color: #fefefe; +} table.button.tiny table td, table.button.tiny table a { - padding: 4px 8px 4px 8px; } + padding: 4px 8px 4px 8px; +} table.button.tiny table a { - font-size: 10px; - font-weight: normal; } + font-size: 10px; + font-weight: normal; +} table.button.small table td, table.button.small table a { - padding: 5px 10px 5px 10px; - font-size: 12px; } + padding: 5px 10px 5px 10px; + font-size: 12px; +} table.button.large table a { - padding: 10px 20px 10px 20px; - font-size: 20px; } + padding: 10px 20px 10px 20px; + font-size: 20px; +} table.button.expand, table.button.expanded { - width: 100% !important; } - table.button.expand table, - table.button.expanded table { - width: 100%; } - table.button.expand table a, - table.button.expanded table a { - text-align: center; - width: 100%; - padding-left: 0; - padding-right: 0; } - table.button.expand center, - table.button.expanded center { - min-width: 0; } + width: 100% !important; +} + +table.button.expand table, +table.button.expanded table { + width: 100%; +} + +table.button.expand table a, +table.button.expanded table a { + text-align: center; + width: 100%; + padding-left: 0; + padding-right: 0; +} + +table.button.expand center, +table.button.expanded center { + min-width: 0; +} table.button:hover table td, table.button:visited table td, table.button:active table td { - background: #147dc2; - color: #fefefe; } + background: #147dc2; + color: #fefefe; +} table.button:hover table a, table.button:visited table a, table.button:active table a { - border: 0 solid #147dc2; } + border: 0 solid #147dc2; +} table.button.secondary table td { - background: #777777; - color: #fefefe; - border: 0px solid #777777; } + background: #777777; + color: #fefefe; + border: 0px solid #777777; +} table.button.secondary table a { - color: #fefefe; - border: 0 solid #777777; } + color: #fefefe; + border: 0 solid #777777; +} table.button.secondary:hover table td { - background: #919191; - color: #fefefe; } + background: #919191; + color: #fefefe; +} table.button.secondary:hover table a { - border: 0 solid #919191; } + border: 0 solid #919191; +} table.button.secondary:hover table td a { - color: #fefefe; } + color: #fefefe; +} table.button.secondary:active table td a { - color: #fefefe; } + color: #fefefe; +} table.button.secondary table td a:visited { - color: #fefefe; } + color: #fefefe; +} table.button.success table td { - background: #3adb76; - border: 0px solid #3adb76; } + background: #3adb76; + border: 0px solid #3adb76; +} table.button.success table a { - border: 0 solid #3adb76; } + border: 0 solid #3adb76; +} table.button.success:hover table td { - background: #23bf5d; } + background: #23bf5d; +} table.button.success:hover table a { - border: 0 solid #23bf5d; } + border: 0 solid #23bf5d; +} table.button.alert table td { - background: #ec5840; - border: 0px solid #ec5840; } + background: #ec5840; + border: 0px solid #ec5840; +} table.button.alert table a { - border: 0 solid #ec5840; } + border: 0 solid #ec5840; +} table.button.alert:hover table td { - background: #e23317; } + background: #e23317; +} table.button.alert:hover table a { - border: 0 solid #e23317; } + border: 0 solid #e23317; +} table.button.warning table td { - background: #ffae00; - border: 0px solid #ffae00; } + background: #ffae00; + border: 0px solid #ffae00; +} table.button.warning table a { - border: 0px solid #ffae00; } + border: 0px solid #ffae00; +} table.button.warning:hover table td { - background: #cc8b00; } + background: #cc8b00; +} table.button.warning:hover table a { - border: 0px solid #cc8b00; } + border: 0px solid #cc8b00; +} table.callout { - margin-bottom: 16px; - Margin-bottom: 16px; } + margin-bottom: 16px; + Margin-bottom: 16px; +} th.callout-inner { - width: 100%; - border: 1px solid #cbcbcb; - padding: 10px; - background: #fefefe; } - th.callout-inner.primary { + width: 100%; + border: 1px solid #cbcbcb; + padding: 10px; + background: #fefefe; +} + +th.callout-inner.primary { background: #def0fc; border: 1px solid #444444; - color: #0a0a0a; } - th.callout-inner.secondary { + color: #0a0a0a; +} + +th.callout-inner.secondary { background: #ebebeb; border: 1px solid #444444; - color: #0a0a0a; } - th.callout-inner.success { + color: #0a0a0a; +} + +th.callout-inner.success { background: #e1faea; border: 1px solid #1b9448; - color: #fefefe; } - th.callout-inner.warning { + color: #fefefe; +} + +th.callout-inner.warning { background: #fff3d9; border: 1px solid #996800; - color: #fefefe; } - th.callout-inner.alert { + color: #fefefe; +} + +th.callout-inner.alert { background: #fce6e2; border: 1px solid #b42912; - color: #fefefe; } + color: #fefefe; +} .thumbnail { - border: solid 4px #fefefe; - box-shadow: 0 0 0 1px rgba(10, 10, 10, 0.2); - display: inline-block; - line-height: 0; - max-width: 100%; - transition: box-shadow 200ms ease-out; - border-radius: 3px; - margin-bottom: 16px; } - .thumbnail:hover, .thumbnail:focus { - box-shadow: 0 0 6px 1px rgba(33, 153, 232, 0.5); } + border: solid 4px #fefefe; + box-shadow: 0 0 0 1px rgba(10, 10, 10, 0.2); + display: inline-block; + line-height: 0; + max-width: 100%; + transition: box-shadow 200ms ease-out; + border-radius: 3px; + margin-bottom: 16px; +} + +.thumbnail:hover, .thumbnail:focus { + box-shadow: 0 0 6px 1px rgba(33, 153, 232, 0.5); +} table.menu { - width: 580px; } - table.menu td.menu-item, - table.menu th.menu-item { + width: 580px; +} + +table.menu td.menu-item, +table.menu th.menu-item { padding: 10px; - padding-right: 10px; } - table.menu td.menu-item a, - table.menu th.menu-item a { - color: #2199e8; } + padding-right: 10px; +} + +table.menu td.menu-item a, +table.menu th.menu-item a { + color: #2199e8; +} table.menu.vertical td.menu-item, table.menu.vertical th.menu-item { - padding: 10px; - padding-right: 0; - display: block; } - table.menu.vertical td.menu-item a, - table.menu.vertical th.menu-item a { - width: 100%; } + padding: 10px; + padding-right: 0; + display: block; +} + +table.menu.vertical td.menu-item a, +table.menu.vertical th.menu-item a { + width: 100%; +} table.menu.vertical td.menu-item table.menu.vertical td.menu-item, table.menu.vertical td.menu-item table.menu.vertical th.menu-item, table.menu.vertical th.menu-item table.menu.vertical td.menu-item, table.menu.vertical th.menu-item table.menu.vertical th.menu-item { - padding-left: 10px; } + padding-left: 10px; +} table.menu.text-center a { - text-align: center; } + text-align: center; +} .menu[align="center"] { - width: auto !important; } + width: auto !important; +} body.outlook p { - display: inline !important; } + display: inline !important; +} @media only screen and (max-width: 596px) { - table.body img { - width: auto; - height: auto; } - table.body center { - min-width: 0 !important; } - table.body .container { - width: 95% !important; } - table.body .columns, - table.body .column { - height: auto !important; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - box-sizing: border-box; - padding-left: 16px !important; - padding-right: 16px !important; } + table.body img { + width: auto; + height: auto; + } + + table.body center { + min-width: 0 !important; + } + + table.body .container { + width: 95% !important; + } + + table.body .columns, + table.body .column { + height: auto !important; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding-left: 16px !important; + padding-right: 16px !important; + } + table.body .columns .column, table.body .columns .columns, table.body .column .column, table.body .column .columns { - padding-left: 0 !important; - padding-right: 0 !important; } - table.body .collapse .columns, - table.body .collapse .column { - padding-left: 0 !important; - padding-right: 0 !important; } - td.small-1, - th.small-1 { - display: inline-block !important; - width: 8.33333% !important; } - td.small-2, - th.small-2 { - display: inline-block !important; - width: 16.66667% !important; } - td.small-3, - th.small-3 { - display: inline-block !important; - width: 25% !important; } - td.small-4, - th.small-4 { - display: inline-block !important; - width: 33.33333% !important; } - td.small-5, - th.small-5 { - display: inline-block !important; - width: 41.66667% !important; } - td.small-6, - th.small-6 { - display: inline-block !important; - width: 50% !important; } - td.small-7, - th.small-7 { - display: inline-block !important; - width: 58.33333% !important; } - td.small-8, - th.small-8 { - display: inline-block !important; - width: 66.66667% !important; } - td.small-9, - th.small-9 { - display: inline-block !important; - width: 75% !important; } - td.small-10, - th.small-10 { - display: inline-block !important; - width: 83.33333% !important; } - td.small-11, - th.small-11 { - display: inline-block !important; - width: 91.66667% !important; } - td.small-12, - th.small-12 { - display: inline-block !important; - width: 100% !important; } - .columns td.small-12, - .column td.small-12, - .columns th.small-12, - .column th.small-12 { - display: block !important; - width: 100% !important; } - table.body td.small-offset-1, - table.body th.small-offset-1 { - margin-left: 8.33333% !important; - Margin-left: 8.33333% !important; } - table.body td.small-offset-2, - table.body th.small-offset-2 { - margin-left: 16.66667% !important; - Margin-left: 16.66667% !important; } - table.body td.small-offset-3, - table.body th.small-offset-3 { - margin-left: 25% !important; - Margin-left: 25% !important; } - table.body td.small-offset-4, - table.body th.small-offset-4 { - margin-left: 33.33333% !important; - Margin-left: 33.33333% !important; } - table.body td.small-offset-5, - table.body th.small-offset-5 { - margin-left: 41.66667% !important; - Margin-left: 41.66667% !important; } - table.body td.small-offset-6, - table.body th.small-offset-6 { - margin-left: 50% !important; - Margin-left: 50% !important; } - table.body td.small-offset-7, - table.body th.small-offset-7 { - margin-left: 58.33333% !important; - Margin-left: 58.33333% !important; } - table.body td.small-offset-8, - table.body th.small-offset-8 { - margin-left: 66.66667% !important; - Margin-left: 66.66667% !important; } - table.body td.small-offset-9, - table.body th.small-offset-9 { - margin-left: 75% !important; - Margin-left: 75% !important; } - table.body td.small-offset-10, - table.body th.small-offset-10 { - margin-left: 83.33333% !important; - Margin-left: 83.33333% !important; } - table.body td.small-offset-11, - table.body th.small-offset-11 { - margin-left: 91.66667% !important; - Margin-left: 91.66667% !important; } - table.body table.columns td.expander, - table.body table.columns th.expander { - display: none !important; } - table.body .right-text-pad, - table.body .text-pad-right { - padding-left: 10px !important; } - table.body .left-text-pad, - table.body .text-pad-left { - padding-right: 10px !important; } - table.menu { - width: 100% !important; } + padding-left: 0 !important; + padding-right: 0 !important; + } + + table.body .collapse .columns, + table.body .collapse .column { + padding-left: 0 !important; + padding-right: 0 !important; + } + + td.small-1, + th.small-1 { + display: inline-block !important; + width: 8.33333% !important; + } + + td.small-2, + th.small-2 { + display: inline-block !important; + width: 16.66667% !important; + } + + td.small-3, + th.small-3 { + display: inline-block !important; + width: 25% !important; + } + + td.small-4, + th.small-4 { + display: inline-block !important; + width: 33.33333% !important; + } + + td.small-5, + th.small-5 { + display: inline-block !important; + width: 41.66667% !important; + } + + td.small-6, + th.small-6 { + display: inline-block !important; + width: 50% !important; + } + + td.small-7, + th.small-7 { + display: inline-block !important; + width: 58.33333% !important; + } + + td.small-8, + th.small-8 { + display: inline-block !important; + width: 66.66667% !important; + } + + td.small-9, + th.small-9 { + display: inline-block !important; + width: 75% !important; + } + + td.small-10, + th.small-10 { + display: inline-block !important; + width: 83.33333% !important; + } + + td.small-11, + th.small-11 { + display: inline-block !important; + width: 91.66667% !important; + } + + td.small-12, + th.small-12 { + display: inline-block !important; + width: 100% !important; + } + + .columns td.small-12, + .column td.small-12, + .columns th.small-12, + .column th.small-12 { + display: block !important; + width: 100% !important; + } + + table.body td.small-offset-1, + table.body th.small-offset-1 { + margin-left: 8.33333% !important; + Margin-left: 8.33333% !important; + } + + table.body td.small-offset-2, + table.body th.small-offset-2 { + margin-left: 16.66667% !important; + Margin-left: 16.66667% !important; + } + + table.body td.small-offset-3, + table.body th.small-offset-3 { + margin-left: 25% !important; + Margin-left: 25% !important; + } + + table.body td.small-offset-4, + table.body th.small-offset-4 { + margin-left: 33.33333% !important; + Margin-left: 33.33333% !important; + } + + table.body td.small-offset-5, + table.body th.small-offset-5 { + margin-left: 41.66667% !important; + Margin-left: 41.66667% !important; + } + + table.body td.small-offset-6, + table.body th.small-offset-6 { + margin-left: 50% !important; + Margin-left: 50% !important; + } + + table.body td.small-offset-7, + table.body th.small-offset-7 { + margin-left: 58.33333% !important; + Margin-left: 58.33333% !important; + } + + table.body td.small-offset-8, + table.body th.small-offset-8 { + margin-left: 66.66667% !important; + Margin-left: 66.66667% !important; + } + + table.body td.small-offset-9, + table.body th.small-offset-9 { + margin-left: 75% !important; + Margin-left: 75% !important; + } + + table.body td.small-offset-10, + table.body th.small-offset-10 { + margin-left: 83.33333% !important; + Margin-left: 83.33333% !important; + } + + table.body td.small-offset-11, + table.body th.small-offset-11 { + margin-left: 91.66667% !important; + Margin-left: 91.66667% !important; + } + + table.body table.columns td.expander, + table.body table.columns th.expander { + display: none !important; + } + + table.body .right-text-pad, + table.body .text-pad-right { + padding-left: 10px !important; + } + + table.body .left-text-pad, + table.body .text-pad-left { + padding-right: 10px !important; + } + + table.menu { + width: 100% !important; + } + table.menu td, table.menu th { - width: auto !important; - display: inline-block !important; } + width: auto !important; + display: inline-block !important; + } + table.menu.vertical td, table.menu.vertical th, table.menu.small-vertical td, table.menu.small-vertical th { - display: block !important; } - table.menu[align="center"] { - width: auto !important; } - table.button.small-expand, - table.button.small-expanded { - width: 100% !important; } + display: block !important; + } + + table.menu[align="center"] { + width: auto !important; + } + + table.button.small-expand, + table.button.small-expanded { + width: 100% !important; + } + table.button.small-expand table, table.button.small-expanded table { - width: 100%; } - table.button.small-expand table a, - table.button.small-expanded table a { + width: 100%; + } + + table.button.small-expand table a, + table.button.small-expanded table a { text-align: center !important; width: 100% !important; padding-left: 0 !important; - padding-right: 0 !important; } + padding-right: 0 !important; + } + table.button.small-expand center, table.button.small-expanded center { - min-width: 0; } } + min-width: 0; + } +} diff --git a/assets/js/app.js b/assets/js/app.js index fd7db935..c0550373 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -28,7 +28,7 @@ import '../css/app/treeview.css'; import '../css/app/images.css'; // start the Stimulus application -import '../bootstrap'; +import '../stimulus_bootstrap'; // Need jQuery? Install it with "yarn add jquery", then uncomment to require it. const $ = require('jquery'); @@ -44,4 +44,18 @@ import "./register_events"; import "./tristate_checkboxes"; //Define jquery globally -window.$ = window.jQuery = require("jquery") \ No newline at end of file +window.$ = window.jQuery = require("jquery"); + +//Use the local WASM file for the ZXing library +import { + setZXingModuleOverrides, +} from "barcode-detector/ponyfill"; +import wasmFile from "../../node_modules/zxing-wasm/dist/reader/zxing_reader.wasm"; +setZXingModuleOverrides({ + locateFile: (path, prefix) => { + if (path.endsWith(".wasm")) { + return wasmFile; + } + return prefix + path; + }, +}); diff --git a/assets/js/lib/dataTables.select.mjs b/assets/js/lib/dataTables.select.mjs new file mode 100644 index 00000000..bba97692 --- /dev/null +++ b/assets/js/lib/dataTables.select.mjs @@ -0,0 +1,1538 @@ +/********************* + * This is the fixed version of the select extension for DataTables with the fix for the issue with the select extension + * (https://github.com/DataTables/Select/issues/51) + * We use this instead of the yarn version until the PR (https://github.com/DataTables/Select/pull/52) is merged and released + * /*******************/ + + +/*! Select for DataTables 2.0.0 + * © SpryMedia Ltd - datatables.net/license/mit + */ + +import jQuery from 'jquery'; +import DataTable from 'datatables.net'; + +// Allow reassignment of the $ variable +let $ = jQuery; + + +// Version information for debugger +DataTable.select = {}; + +DataTable.select.version = '2.0.0'; + +DataTable.select.init = function (dt) { + var ctx = dt.settings()[0]; + + if (!DataTable.versionCheck('2')) { + throw 'Warning: Select requires DataTables 2 or newer'; + } + + if (ctx._select) { + return; + } + + var savedSelected = dt.state.loaded(); + + var selectAndSave = function (e, settings, data) { + if (data === null || data.select === undefined) { + return; + } + + // Clear any currently selected rows, before restoring state + // None will be selected on first initialisation + if (dt.rows({ selected: true }).any()) { + dt.rows().deselect(); + } + if (data.select.rows !== undefined) { + dt.rows(data.select.rows).select(); + } + + if (dt.columns({ selected: true }).any()) { + dt.columns().deselect(); + } + if (data.select.columns !== undefined) { + dt.columns(data.select.columns).select(); + } + + if (dt.cells({ selected: true }).any()) { + dt.cells().deselect(); + } + if (data.select.cells !== undefined) { + for (var i = 0; i < data.select.cells.length; i++) { + dt.cell(data.select.cells[i].row, data.select.cells[i].column).select(); + } + } + + dt.state.save(); + }; + + dt.on('stateSaveParams', function (e, settings, data) { + data.select = {}; + data.select.rows = dt.rows({ selected: true }).ids(true).toArray(); + data.select.columns = dt.columns({ selected: true })[0]; + data.select.cells = dt.cells({ selected: true })[0].map(function (coords) { + return { row: dt.row(coords.row).id(true), column: coords.column }; + }); + }) + .on('stateLoadParams', selectAndSave) + .one('init', function () { + selectAndSave(undefined, undefined, savedSelected); + }); + + var init = ctx.oInit.select; + var defaults = DataTable.defaults.select; + var opts = init === undefined ? defaults : init; + + // Set defaults + var items = 'row'; + var style = 'api'; + var blurable = false; + var toggleable = true; + var info = true; + var selector = 'td, th'; + var className = 'selected'; + var headerCheckbox = true; + var setStyle = false; + + ctx._select = { + infoEls: [] + }; + + // Initialisation customisations + if (opts === true) { + style = 'os'; + setStyle = true; + } + else if (typeof opts === 'string') { + style = opts; + setStyle = true; + } + else if ($.isPlainObject(opts)) { + if (opts.blurable !== undefined) { + blurable = opts.blurable; + } + + if (opts.toggleable !== undefined) { + toggleable = opts.toggleable; + } + + if (opts.info !== undefined) { + info = opts.info; + } + + if (opts.items !== undefined) { + items = opts.items; + } + + if (opts.style !== undefined) { + style = opts.style; + setStyle = true; + } + else { + style = 'os'; + setStyle = true; + } + + if (opts.selector !== undefined) { + selector = opts.selector; + } + + if (opts.className !== undefined) { + className = opts.className; + } + + if (opts.headerCheckbox !== undefined) { + headerCheckbox = opts.headerCheckbox; + } + } + + dt.select.selector(selector); + dt.select.items(items); + dt.select.style(style); + dt.select.blurable(blurable); + dt.select.toggleable(toggleable); + dt.select.info(info); + ctx._select.className = className; + + // If the init options haven't enabled select, but there is a selectable + // class name, then enable + if (!setStyle && $(dt.table().node()).hasClass('selectable')) { + dt.select.style('os'); + } + + // Insert a checkbox into the header if needed - might need to wait + // for init complete, or it might already be done + if (headerCheckbox) { + initCheckboxHeader(dt); + + dt.on('init', function () { + initCheckboxHeader(dt); + }); + } +}; + +/* + +Select is a collection of API methods, event handlers, event emitters and +buttons (for the `Buttons` extension) for DataTables. It provides the following +features, with an overview of how they are implemented: + +## Selection of rows, columns and cells. Whether an item is selected or not is + stored in: + +* rows: a `_select_selected` property which contains a boolean value of the + DataTables' `aoData` object for each row +* columns: a `_select_selected` property which contains a boolean value of the + DataTables' `aoColumns` object for each column +* cells: a `_selected_cells` property which contains an array of boolean values + of the `aoData` object for each row. The array is the same length as the + columns array, with each element of it representing a cell. + +This method of using boolean flags allows Select to operate when nodes have not +been created for rows / cells (DataTables' defer rendering feature). + +## API methods + +A range of API methods are available for triggering selection and de-selection +of rows. Methods are also available to configure the selection events that can +be triggered by an end user (such as which items are to be selected). To a large +extent, these of API methods *is* Select. It is basically a collection of helper +functions that can be used to select items in a DataTable. + +Configuration of select is held in the object `_select` which is attached to the +DataTables settings object on initialisation. Select being available on a table +is not optional when Select is loaded, but its default is for selection only to +be available via the API - so the end user wouldn't be able to select rows +without additional configuration. + +The `_select` object contains the following properties: + +``` +{ + items:string - Can be `rows`, `columns` or `cells`. Defines what item + will be selected if the user is allowed to activate row + selection using the mouse. + style:string - Can be `none`, `single`, `multi` or `os`. Defines the + interaction style when selecting items + blurable:boolean - If row selection can be cleared by clicking outside of + the table + toggleable:boolean - If row selection can be cancelled by repeated clicking + on the row + info:boolean - If the selection summary should be shown in the table + information elements + infoEls:element[] - List of HTML elements with info elements for a table +} +``` + +In addition to the API methods, Select also extends the DataTables selector +options for rows, columns and cells adding a `selected` option to the selector +options object, allowing the developer to select only selected items or +unselected items. + +## Mouse selection of items + +Clicking on items can be used to select items. This is done by a simple event +handler that will select the items using the API methods. + + */ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Local functions + */ + +/** + * Add one or more cells to the selection when shift clicking in OS selection + * style cell selection. + * + * Cell range is more complicated than row and column as we want to select + * in the visible grid rather than by index in sequence. For example, if you + * click first in cell 1-1 and then shift click in 2-2 - cells 1-2 and 2-1 + * should also be selected (and not 1-3, 1-4. etc) + * + * @param {DataTable.Api} dt DataTable + * @param {object} idx Cell index to select to + * @param {object} last Cell index to select from + * @private + */ +function cellRange(dt, idx, last) { + var indexes; + var columnIndexes; + var rowIndexes; + var selectColumns = function (start, end) { + if (start > end) { + var tmp = end; + end = start; + start = tmp; + } + + var record = false; + return dt + .columns(':visible') + .indexes() + .filter(function (i) { + if (i === start) { + record = true; + } + + if (i === end) { + // not else if, as start might === end + record = false; + return true; + } + + return record; + }); + }; + + var selectRows = function (start, end) { + var indexes = dt.rows({ search: 'applied' }).indexes(); + + // Which comes first - might need to swap + if (indexes.indexOf(start) > indexes.indexOf(end)) { + var tmp = end; + end = start; + start = tmp; + } + + var record = false; + return indexes.filter(function (i) { + if (i === start) { + record = true; + } + + if (i === end) { + record = false; + return true; + } + + return record; + }); + }; + + if (!dt.cells({ selected: true }).any() && !last) { + // select from the top left cell to this one + columnIndexes = selectColumns(0, idx.column); + rowIndexes = selectRows(0, idx.row); + } + else { + // Get column indexes between old and new + columnIndexes = selectColumns(last.column, idx.column); + rowIndexes = selectRows(last.row, idx.row); + } + + indexes = dt.cells(rowIndexes, columnIndexes).flatten(); + + if (!dt.cells(idx, { selected: true }).any()) { + // Select range + dt.cells(indexes).select(); + } + else { + // Deselect range + dt.cells(indexes).deselect(); + } +} + +/** + * Disable mouse selection by removing the selectors + * + * @param {DataTable.Api} dt DataTable to remove events from + * @private + */ +function disableMouseSelection(dt) { + var ctx = dt.settings()[0]; + var selector = ctx._select.selector; + + $(dt.table().container()) + .off('mousedown.dtSelect', selector) + .off('mouseup.dtSelect', selector) + .off('click.dtSelect', selector); + + $('body').off('click.dtSelect' + _safeId(dt.table().node())); +} + +/** + * Attach mouse listeners to the table to allow mouse selection of items + * + * @param {DataTable.Api} dt DataTable to remove events from + * @private + */ +function enableMouseSelection(dt) { + var container = $(dt.table().container()); + var ctx = dt.settings()[0]; + var selector = ctx._select.selector; + var matchSelection; + + container + .on('mousedown.dtSelect', selector, function (e) { + // Disallow text selection for shift clicking on the table so multi + // element selection doesn't look terrible! + if (e.shiftKey || e.metaKey || e.ctrlKey) { + container + .css('-moz-user-select', 'none') + .one('selectstart.dtSelect', selector, function () { + return false; + }); + } + + if (window.getSelection) { + matchSelection = window.getSelection(); + } + }) + .on('mouseup.dtSelect', selector, function () { + // Allow text selection to occur again, Mozilla style (tested in FF + // 35.0.1 - still required) + container.css('-moz-user-select', ''); + }) + .on('click.dtSelect', selector, function (e) { + var items = dt.select.items(); + var idx; + + // If text was selected (click and drag), then we shouldn't change + // the row's selected state + if (matchSelection) { + var selection = window.getSelection(); + + // If the element that contains the selection is not in the table, we can ignore it + // This can happen if the developer selects text from the click event + if ( + !selection.anchorNode || + $(selection.anchorNode).closest('table')[0] === dt.table().node() + ) { + if (selection !== matchSelection) { + return; + } + } + } + + var ctx = dt.settings()[0]; + var container = dt.table().container(); + + // Ignore clicks inside a sub-table + if ($(e.target).closest('div.dt-container')[0] != container) { + return; + } + + var cell = dt.cell($(e.target).closest('td, th')); + + // Check the cell actually belongs to the host DataTable (so child + // rows, etc, are ignored) + if (!cell.any()) { + return; + } + + var event = $.Event('user-select.dt'); + eventTrigger(dt, event, [items, cell, e]); + + if (event.isDefaultPrevented()) { + return; + } + + var cellIndex = cell.index(); + if (items === 'row') { + idx = cellIndex.row; + typeSelect(e, dt, ctx, 'row', idx); + } + else if (items === 'column') { + idx = cell.index().column; + typeSelect(e, dt, ctx, 'column', idx); + } + else if (items === 'cell') { + idx = cell.index(); + typeSelect(e, dt, ctx, 'cell', idx); + } + + ctx._select_lastCell = cellIndex; + }); + + // Blurable + $('body').on('click.dtSelect' + _safeId(dt.table().node()), function (e) { + if (ctx._select.blurable) { + // If the click was inside the DataTables container, don't blur + if ($(e.target).parents().filter(dt.table().container()).length) { + return; + } + + // Ignore elements which have been removed from the DOM (i.e. paging + // buttons) + if ($(e.target).parents('html').length === 0) { + return; + } + + // Don't blur in Editor form + if ($(e.target).parents('div.DTE').length) { + return; + } + + var event = $.Event('select-blur.dt'); + eventTrigger(dt, event, [e.target, e]); + + if (event.isDefaultPrevented()) { + return; + } + + clear(ctx, true); + } + }); +} + +/** + * Trigger an event on a DataTable + * + * @param {DataTable.Api} api DataTable to trigger events on + * @param {boolean} selected true if selected, false if deselected + * @param {string} type Item type acting on + * @param {boolean} any Require that there are values before + * triggering + * @private + */ +function eventTrigger(api, type, args, any) { + if (any && !api.flatten().length) { + return; + } + + if (typeof type === 'string') { + type = type + '.dt'; + } + + args.unshift(api); + + $(api.table().node()).trigger(type, args); +} + +/** + * Update the information element of the DataTable showing information about the + * items selected. This is done by adding tags to the existing text + * + * @param {DataTable.Api} api DataTable to update + * @private + */ +function info(api, node) { + if (api.select.style() === 'api' || api.select.info() === false) { + return; + } + + var rows = api.rows({ selected: true }).flatten().length; + var columns = api.columns({ selected: true }).flatten().length; + var cells = api.cells({ selected: true }).flatten().length; + + var add = function (el, name, num) { + el.append( + $('').append( + api.i18n( + 'select.' + name + 's', + { _: '%d ' + name + 's selected', 0: '', 1: '1 ' + name + ' selected' }, + num + ) + ) + ); + }; + + var el = $(node); + var output = $(''); + + add(output, 'row', rows); + add(output, 'column', columns); + add(output, 'cell', cells); + + var existing = el.children('span.select-info'); + + if (existing.length) { + existing.remove(); + } + + if (output.text() !== '') { + el.append(output); + } +} + +/** + * Add a checkbox to the header for checkbox columns, allowing all rows to + * be selected, deselected or just to show the state. + * + * @param {*} dt API + */ +function initCheckboxHeader( dt ) { + // Find any checkbox column(s) + dt.columns('.dt-select').every(function () { + var header = this.header(); + + if (! $('input', header).length) { + // If no checkbox yet, insert one + var input = $('') + .attr({ + class: 'dt-select-checkbox', + type: 'checkbox', + 'aria-label': dt.i18n('select.aria.headerCheckbox') || 'Select all rows' + }) + .appendTo(header) + .on('change', function () { + if (this.checked) { + dt.rows({search: 'applied'}).select(); + } + else { + dt.rows({selected: true}).deselect(); + } + }) + .on('click', function (e) { + e.stopPropagation(); + }); + + // Update the header checkbox's state when the selection in the + // table changes + dt.on('draw select deselect', function (e, pass, type) { + if (type === 'row' || ! type) { + var count = dt.rows({selected: true}).count(); + var search = dt.rows({search: 'applied', selected: true}).count(); + var available = dt.rows({search: 'applied'}).count(); + + if (search && search <= count && search === available) { + input + .prop('checked', true) + .prop('indeterminate', false); + } + else if (search === 0 && count === 0) { + input + .prop('checked', false) + .prop('indeterminate', false); + } + else { + input + .prop('checked', false) + .prop('indeterminate', true); + } + } + }); + } + }); +} + +/** + * Initialisation of a new table. Attach event handlers and callbacks to allow + * Select to operate correctly. + * + * This will occur _after_ the initial DataTables initialisation, although + * before Ajax data is rendered, if there is ajax data + * + * @param {DataTable.settings} ctx Settings object to operate on + * @private + */ +function init(ctx) { + var api = new DataTable.Api(ctx); + ctx._select_init = true; + + // Row callback so that classes can be added to rows and cells if the item + // was selected before the element was created. This will happen with the + // `deferRender` option enabled. + // + // This method of attaching to `aoRowCreatedCallback` is a hack until + // DataTables has proper events for row manipulation If you are reviewing + // this code to create your own plug-ins, please do not do this! + ctx.aoRowCreatedCallback.push(function (row, data, index) { + var i, ien; + var d = ctx.aoData[index]; + + // Row + if (d._select_selected) { + $(row).addClass(ctx._select.className); + } + + // Cells and columns - if separated out, we would need to do two + // loops, so it makes sense to combine them into a single one + for (i = 0, ien = ctx.aoColumns.length; i < ien; i++) { + if ( + ctx.aoColumns[i]._select_selected || + (d._selected_cells && d._selected_cells[i]) + ) { + $(d.anCells[i]).addClass(ctx._select.className); + } + } + } + ); + + // On Ajax reload we want to reselect all rows which are currently selected, + // if there is an rowId (i.e. a unique value to identify each row with) + api.on('preXhr.dt.dtSelect', function (e, settings) { + if (settings !== api.settings()[0]) { + // Not triggered by our DataTable! + return; + } + + // note that column selection doesn't need to be cached and then + // reselected, as they are already selected + var rows = api + .rows({ selected: true }) + .ids(true) + .filter(function (d) { + return d !== undefined; + }); + + var cells = api + .cells({ selected: true }) + .eq(0) + .map(function (cellIdx) { + var id = api.row(cellIdx.row).id(true); + return id ? { row: id, column: cellIdx.column } : undefined; + }) + .filter(function (d) { + return d !== undefined; + }); + + // On the next draw, reselect the currently selected items + api.one('draw.dt.dtSelect', function () { + api.rows(rows).select(); + + // `cells` is not a cell index selector, so it needs a loop + if (cells.any()) { + cells.each(function (id) { + api.cells(id.row, id.column).select(); + }); + } + }); + }); + + // Update the table information element with selected item summary + api.on('info.dt', function (e, ctx, node) { + // Store the info node for updating on select / deselect + if (!ctx._select.infoEls.includes(node)) { + ctx._select.infoEls.push(node); + } + + info(api, node); + }); + + api.on('select.dtSelect.dt deselect.dtSelect.dt', function () { + ctx._select.infoEls.forEach(function (el) { + info(api, el); + }); + + api.state.save(); + }); + + // Clean up and release + api.on('destroy.dtSelect', function () { + // Remove class directly rather than calling deselect - which would trigger events + $(api.rows({ selected: true }).nodes()).removeClass(api.settings()[0]._select.className); + + disableMouseSelection(api); + api.off('.dtSelect'); + $('body').off('.dtSelect' + _safeId(api.table().node())); + }); +} + +/** + * Add one or more items (rows or columns) to the selection when shift clicking + * in OS selection style + * + * @param {DataTable.Api} dt DataTable + * @param {string} type Row or column range selector + * @param {object} idx Item index to select to + * @param {object} last Item index to select from + * @private + */ +function rowColumnRange(dt, type, idx, last) { + // Add a range of rows from the last selected row to this one + var indexes = dt[type + 's']({ search: 'applied' }).indexes(); + var idx1 = indexes.indexOf(last); + var idx2 = indexes.indexOf(idx); + + if (!dt[type + 's']({ selected: true }).any() && idx1 === -1) { + // select from top to here - slightly odd, but both Windows and Mac OS + // do this + indexes.splice(indexes.indexOf(idx) + 1, indexes.length); + } + else { + // reverse so we can shift click 'up' as well as down + if (idx1 > idx2) { + var tmp = idx2; + idx2 = idx1; + idx1 = tmp; + } + + indexes.splice(idx2 + 1, indexes.length); + indexes.splice(0, idx1); + } + + if (!dt[type](idx, { selected: true }).any()) { + // Select range + dt[type + 's'](indexes).select(); + } + else { + // Deselect range - need to keep the clicked on row selected + indexes.splice(indexes.indexOf(idx), 1); + dt[type + 's'](indexes).deselect(); + } +} + +/** + * Clear all selected items + * + * @param {DataTable.settings} ctx Settings object of the host DataTable + * @param {boolean} [force=false] Force the de-selection to happen, regardless + * of selection style + * @private + */ +function clear(ctx, force) { + if (force || ctx._select.style === 'single') { + var api = new DataTable.Api(ctx); + + api.rows({ selected: true }).deselect(); + api.columns({ selected: true }).deselect(); + api.cells({ selected: true }).deselect(); + } +} + +/** + * Select items based on the current configuration for style and items. + * + * @param {object} e Mouse event object + * @param {DataTables.Api} dt DataTable + * @param {DataTable.settings} ctx Settings object of the host DataTable + * @param {string} type Items to select + * @param {int|object} idx Index of the item to select + * @private + */ +function typeSelect(e, dt, ctx, type, idx) { + var style = dt.select.style(); + var toggleable = dt.select.toggleable(); + var isSelected = dt[type](idx, { selected: true }).any(); + + if (isSelected && !toggleable) { + return; + } + + if (style === 'os') { + if (e.ctrlKey || e.metaKey) { + // Add or remove from the selection + dt[type](idx).select(!isSelected); + } + else if (e.shiftKey) { + if (type === 'cell') { + cellRange(dt, idx, ctx._select_lastCell || null); + } + else { + rowColumnRange( + dt, + type, + idx, + ctx._select_lastCell ? ctx._select_lastCell[type] : null + ); + } + } + else { + // No cmd or shift click - deselect if selected, or select + // this row only + var selected = dt[type + 's']({ selected: true }); + + if (isSelected && selected.flatten().length === 1) { + dt[type](idx).deselect(); + } + else { + selected.deselect(); + dt[type](idx).select(); + } + } + } + else if (style == 'multi+shift') { + if (e.shiftKey) { + if (type === 'cell') { + cellRange(dt, idx, ctx._select_lastCell || null); + } + else { + rowColumnRange( + dt, + type, + idx, + ctx._select_lastCell ? ctx._select_lastCell[type] : null + ); + } + } + else { + dt[type](idx).select(!isSelected); + } + } + else { + dt[type](idx).select(!isSelected); + } +} + +function _safeId(node) { + return node.id.replace(/[^a-zA-Z0-9\-\_]/g, '-'); +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables selectors + */ + +// row and column are basically identical just assigned to different properties +// and checking a different array, so we can dynamically create the functions to +// reduce the code size +$.each( + [ + { type: 'row', prop: 'aoData' }, + { type: 'column', prop: 'aoColumns' } + ], + function (i, o) { + DataTable.ext.selector[o.type].push(function (settings, opts, indexes) { + var selected = opts.selected; + var data; + var out = []; + + if (selected !== true && selected !== false) { + return indexes; + } + + for (var i = 0, ien = indexes.length; i < ien; i++) { + data = settings[o.prop][indexes[i]]; + + if ( + data && ( + (selected === true && data._select_selected === true) || + (selected === false && !data._select_selected) + ) + ) { + out.push(indexes[i]); + } + } + + return out; + }); + } +); + +DataTable.ext.selector.cell.push(function (settings, opts, cells) { + var selected = opts.selected; + var rowData; + var out = []; + + if (selected === undefined) { + return cells; + } + + for (var i = 0, ien = cells.length; i < ien; i++) { + rowData = settings.aoData[cells[i].row]; + + if ( + rowData && ( + (selected === true && + rowData._selected_cells && + rowData._selected_cells[cells[i].column] === true) || + (selected === false && + (!rowData._selected_cells || !rowData._selected_cells[cells[i].column])) + ) + ) { + out.push(cells[i]); + } + } + + return out; +}); + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * DataTables API + * + * For complete documentation, please refer to the docs/api directory or the + * DataTables site + */ + +// Local variables to improve compression +var apiRegister = DataTable.Api.register; +var apiRegisterPlural = DataTable.Api.registerPlural; + +apiRegister('select()', function () { + return this.iterator('table', function (ctx) { + DataTable.select.init(new DataTable.Api(ctx)); + }); +}); + +apiRegister('select.blurable()', function (flag) { + if (flag === undefined) { + return this.context[0]._select.blurable; + } + + return this.iterator('table', function (ctx) { + ctx._select.blurable = flag; + }); +}); + +apiRegister('select.toggleable()', function (flag) { + if (flag === undefined) { + return this.context[0]._select.toggleable; + } + + return this.iterator('table', function (ctx) { + ctx._select.toggleable = flag; + }); +}); + +apiRegister('select.info()', function (flag) { + if (flag === undefined) { + return this.context[0]._select.info; + } + + return this.iterator('table', function (ctx) { + ctx._select.info = flag; + }); +}); + +apiRegister('select.items()', function (items) { + if (items === undefined) { + return this.context[0]._select.items; + } + + return this.iterator('table', function (ctx) { + ctx._select.items = items; + + eventTrigger(new DataTable.Api(ctx), 'selectItems', [items]); + }); +}); + +// Takes effect from the _next_ selection. None disables future selection, but +// does not clear the current selection. Use the `deselect` methods for that +apiRegister('select.style()', function (style) { + if (style === undefined) { + return this.context[0]._select.style; + } + + return this.iterator('table', function (ctx) { + if (!ctx._select) { + DataTable.select.init(new DataTable.Api(ctx)); + } + + if (!ctx._select_init) { + init(ctx); + } + + ctx._select.style = style; + + // Add / remove mouse event handlers. They aren't required when only + // API selection is available + var dt = new DataTable.Api(ctx); + disableMouseSelection(dt); + + if (style !== 'api') { + enableMouseSelection(dt); + } + + eventTrigger(new DataTable.Api(ctx), 'selectStyle', [style]); + }); +}); + +apiRegister('select.selector()', function (selector) { + if (selector === undefined) { + return this.context[0]._select.selector; + } + + return this.iterator('table', function (ctx) { + disableMouseSelection(new DataTable.Api(ctx)); + + ctx._select.selector = selector; + + if (ctx._select.style !== 'api') { + enableMouseSelection(new DataTable.Api(ctx)); + } + }); +}); + +apiRegister('select.last()', function (set) { + let ctx = this.context[0]; + + if (set) { + ctx._select_lastCell = set; + return this; + } + + return ctx._select_lastCell; +}); + +apiRegisterPlural('rows().select()', 'row().select()', function (select) { + var api = this; + + if (select === false) { + return this.deselect(); + } + + this.iterator('row', function (ctx, idx) { + clear(ctx); + + // There is a good amount of knowledge of DataTables internals in + // this function. It _could_ be done without that, but it would hurt + // performance (or DT would need new APIs for this work) + var dtData = ctx.aoData[idx]; + var dtColumns = ctx.aoColumns; + + $(dtData.nTr).addClass(ctx._select.className); + dtData._select_selected = true; + + for (var i=0 ; i 0); + }); + + this.disable(); + }, + destroy: function (dt, node, config) { + dt.off(config._eventNamespace); + } + }, + showSelected: { + text: i18n('showSelected', 'Show only selected'), + className: 'buttons-show-selected', + action: function (e, dt) { + if (dt.search.fixed('dt-select')) { + // Remove existing function + dt.search.fixed('dt-select', null); + + this.active(false); + } + else { + // Use a fixed filtering function to match on selected rows + // This needs to reference the internal aoData since that is + // where Select stores its reference for the selected state + var dataSrc = dt.settings()[0].aoData; + + dt.search.fixed('dt-select', function (text, data, idx) { + // _select_selected is set by Select on the data object for the row + return dataSrc[idx]._select_selected; + }); + + this.active(true); + } + + dt.draw(); + } + } +}); + +$.each(['Row', 'Column', 'Cell'], function (i, item) { + var lc = item.toLowerCase(); + + DataTable.ext.buttons['select' + item + 's'] = { + text: i18n('select' + item + 's', 'Select ' + lc + 's'), + className: 'buttons-select-' + lc + 's', + action: function () { + this.select.items(lc); + }, + init: function (dt) { + var that = this; + + dt.on('selectItems.dt.DT', function (e, ctx, items) { + that.active(items === lc); + }); + } + }; +}); + +DataTable.type('select-checkbox', { + className: 'dt-select', + detect: function (data) { + // Rendering function will tell us if it is a checkbox type + return data === 'select-checkbox' ? data : false; + }, + order: { + pre: function (d) { + return d === 'X' ? -1 : 0; + } + } +}); + +$.extend(true, DataTable.defaults.oLanguage, { + select: { + aria: { + rowCheckbox: 'Select row' + } + } +}); + +DataTable.render.select = function (valueProp, nameProp) { + var valueFn = valueProp ? DataTable.util.get(valueProp) : null; + var nameFn = nameProp ? DataTable.util.get(nameProp) : null; + + return function (data, type, row, meta) { + var dtRow = meta.settings.aoData[meta.row]; + var selected = dtRow._select_selected; + var ariaLabel = meta.settings.oLanguage.select.aria.rowCheckbox; + + if (type === 'display') { + return $('') + .attr({ + 'aria-label': ariaLabel, + class: 'dt-select-checkbox', + name: nameFn ? nameFn(row) : null, + type: 'checkbox', + value: valueFn ? valueFn(row) : null, + checked: selected + })[0]; + } + else if (type === 'type') { + return 'select-checkbox'; + } + else if (type === 'filter') { + return ''; + } + + return selected ? 'X' : ''; + } +} + +// Legacy checkbox ordering +DataTable.ext.order['select-checkbox'] = function (settings, col) { + return this.api() + .column(col, { order: 'index' }) + .nodes() + .map(function (td) { + if (settings._select.items === 'row') { + return $(td).parent().hasClass(settings._select.className); + } + else if (settings._select.items === 'cell') { + return $(td).hasClass(settings._select.className); + } + return false; + }); +}; + +$.fn.DataTable.select = DataTable.select; + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Initialisation + */ + +// DataTables creation - check if select has been defined in the options. Note +// this required that the table be in the document! If it isn't then something +// needs to trigger this method unfortunately. The next major release of +// DataTables will rework the events and address this. +$(document).on('preInit.dt.dtSelect', function (e, ctx) { + if (e.namespace !== 'dt') { + return; + } + + DataTable.select.init(new DataTable.Api(ctx)); +}); + + +export default DataTable; diff --git a/assets/js/lib/datatables.js b/assets/js/lib/datatables.js index 8c76d89d..67bab02d 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"); }); } } @@ -98,6 +97,15 @@ dtOpts = config.options(dtOpts); } + //Choose the column where the className contains "select-column" and apply the select extension to its render field + //Added for Part-DB + for (let column of dtOpts.columns) { + if (column.className && column.className.includes('dt-select')) { + column.render = $.fn.dataTable.render.select(); + } + } + + root.html(data.template); dt = $('table', root).DataTable(dtOpts); if (config.state !== 'none') { diff --git a/assets/js/register_events.js b/assets/js/register_events.js index 22e91fdf..9732c0c1 100644 --- a/assets/js/register_events.js +++ b/assets/js/register_events.js @@ -20,6 +20,8 @@ 'use strict'; import {Dropdown} from "bootstrap"; +import ClipboardJS from "clipboard"; +import {Modal} from "bootstrap"; class RegisterEventHelper { constructor() { @@ -27,7 +29,14 @@ class RegisterEventHelper { this.configureDropdowns(); this.registerSpecialCharInput(); + //Initialize ClipboardJS + this.registerLoadHandler(() => { + new ClipboardJS('.btn'); + }); + this.registerModalDropRemovalOnFormSubmit(); + + } registerModalDropRemovalOnFormSubmit() { @@ -37,6 +46,15 @@ class RegisterEventHelper { if (back_drop) { back_drop.remove(); } + + //Remove scroll-lock if it is still active + if (document.body.classList.contains('modal-open')) { + document.body.classList.remove('modal-open'); + + //Remove the padding-right and overflow:hidden from the body + document.body.style.paddingRight = ''; + document.body.style.overflow = ''; + } }); } diff --git a/assets/js/tab_remember.js b/assets/js/tab_remember.js index 9ecd71c5..1bf35db5 100644 --- a/assets/js/tab_remember.js +++ b/assets/js/tab_remember.js @@ -19,7 +19,7 @@ "use strict"; -import {Tab, Dropdown} from "bootstrap"; +import {Tab, Dropdown, Collapse} from "bootstrap"; import tab from "bootstrap/js/src/tab"; /** @@ -54,6 +54,7 @@ class TabRememberHelper { const first_element = merged[0] ?? null; if(first_element) { this.revealElementOnTab(first_element); + this.revealElementInCollapse(first_element); } } @@ -62,10 +63,20 @@ class TabRememberHelper { * @param event */ onInvalid(event) { + this.revealElementInCollapse(event.target); this.revealElementOnTab(event.target); this.revealElementInDropdown(event.target); } + revealElementInCollapse(element) { + let collapse = element.closest('.collapse'); + + if(collapse) { + let bs_collapse = Collapse.getOrCreateInstance(collapse); + bs_collapse.show(); + } + } + revealElementInDropdown(element) { let dropdown = element.closest('.dropdown-menu'); diff --git a/assets/bootstrap.js b/assets/stimulus_bootstrap.js similarity index 100% rename from assets/bootstrap.js rename to assets/stimulus_bootstrap.js diff --git a/assets/tomselect/autoselect_typed/autoselect_typed.js b/assets/tomselect/autoselect_typed/autoselect_typed.js new file mode 100644 index 00000000..8a426be7 --- /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 00000000..b7dcab03 --- /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/bin/console b/bin/console index c933dc53..256c0a60 100755 --- a/bin/console +++ b/bin/console @@ -4,6 +4,17 @@ use App\Kernel; use Symfony\Bundle\FrameworkBundle\Console\Application; +if (!is_dir(dirname(__DIR__).'/vendor')) { + throw new LogicException('Dependencies are missing. Try running "composer install".'); +} + +//Increase xdebug.max_nesting_level to 1000 if required (see issue #411) +//Check if xdebug extension is active, and xdebug.max_nesting_level is set to 256 or lower +if (extension_loaded('xdebug') && ((int) ini_get('xdebug.max_nesting_level')) <= 256) { + //Increase xdebug.max_nesting_level to 1000 + ini_set('xdebug.max_nesting_level', '1000'); +} + if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) { throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".'); } diff --git a/bin/phpunit b/bin/phpunit index f26f2c72..ac5eef11 100755 --- a/bin/phpunit +++ b/bin/phpunit @@ -1,19 +1,4 @@ #!/usr/bin/env php =8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Future/functions.php", + "src/Internal/functions.php" + ], + "psr-4": { + "Amp\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-08-27T21:42:00+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/parser": "^1.1", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2.3" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.22.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "https://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v2.1.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-03-16T17:10:27+00:00" + }, + { + "name": "amphp/cache", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/cache.git", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/cache/zipball/46912e387e6aa94933b61ea1ead9cf7540b7797c", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Cache\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + } + ], + "description": "A fiber-aware cache API based on Amp and Revolt.", + "homepage": "https://amphp.org/cache", + "support": { + "issues": "https://github.com/amphp/cache/issues", + "source": "https://github.com/amphp/cache/tree/v2.0.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T03:38:06+00:00" + }, + { + "name": "amphp/dns", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/dns.git", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/dns/zipball/78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/cache": "^2", + "amphp/parser": "^1", + "amphp/process": "^2", + "daverandom/libdns": "^2.0.2", + "ext-filter": "*", + "ext-json": "*", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Dns\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Wright", + "email": "addr@daverandom.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "Async DNS resolution for Amp.", + "homepage": "https://github.com/amphp/dns", + "keywords": [ + "amp", + "amphp", + "async", + "client", + "dns", + "resolve" + ], + "support": { + "issues": "https://github.com/amphp/dns/issues", + "source": "https://github.com/amphp/dns/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-01-19T15:43:40+00:00" + }, + { + "name": "amphp/hpack", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/hpack.git", + "reference": "4f293064b15682a2b178b1367ddf0b8b5feb0239" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/hpack/zipball/4f293064b15682a2b178b1367ddf0b8b5feb0239", + "reference": "4f293064b15682a2b178b1367ddf0b8b5feb0239", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "http2jp/hpack-test-case": "^1", + "nikic/php-fuzzer": "^0.0.10", + "phpunit/phpunit": "^7 | ^8 | ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Amp\\Http\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Bob Weinand" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "HTTP/2 HPack implementation.", + "homepage": "https://github.com/amphp/hpack", + "keywords": [ + "headers", + "hpack", + "http-2" + ], + "support": { + "issues": "https://github.com/amphp/hpack/issues", + "source": "https://github.com/amphp/hpack/tree/v3.2.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-03-21T19:00:16+00:00" + }, + { + "name": "amphp/http", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/http.git", + "reference": "3680d80bd38b5d6f3c2cef2214ca6dd6cef26588" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/http/zipball/3680d80bd38b5d6f3c2cef2214ca6dd6cef26588", + "reference": "3680d80bd38b5d6f3c2cef2214ca6dd6cef26588", + "shasum": "" + }, + "require": { + "amphp/hpack": "^3", + "amphp/parser": "^1.1", + "league/uri-components": "^2.4.2 | ^7.1", + "php": ">=8.1", + "psr/http-message": "^1 | ^2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "league/uri": "^6.8 | ^7.1", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.26.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/constants.php" + ], + "psr-4": { + "Amp\\Http\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "Basic HTTP primitives which can be shared by servers and clients.", + "support": { + "issues": "https://github.com/amphp/http/issues", + "source": "https://github.com/amphp/http/tree/v2.1.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-11-23T14:57:26+00:00" + }, + { + "name": "amphp/http-client", + "version": "v5.3.4", + "source": { + "type": "git", + "url": "https://github.com/amphp/http-client.git", + "reference": "75ad21574fd632594a2dd914496647816d5106bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/http-client/zipball/75ad21574fd632594a2dd914496647816d5106bc", + "reference": "75ad21574fd632594a2dd914496647816d5106bc", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/hpack": "^3", + "amphp/http": "^2", + "amphp/pipeline": "^1", + "amphp/socket": "^2", + "amphp/sync": "^2", + "league/uri": "^7", + "league/uri-components": "^7", + "league/uri-interfaces": "^7.1", + "php": ">=8.1", + "psr/http-message": "^1 | ^2", + "revolt/event-loop": "^1" + }, + "conflict": { + "amphp/file": "<3 | >=5" + }, + "require-dev": { + "amphp/file": "^3 | ^4", + "amphp/http-server": "^3", + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "ext-json": "*", + "kelunik/link-header-rfc5988": "^1", + "laminas/laminas-diactoros": "^2.3", + "phpunit/phpunit": "^9", + "psalm/phar": "~5.23" + }, + "suggest": { + "amphp/file": "Required for file request bodies and HTTP archive logging", + "ext-json": "Required for logging HTTP archives", + "ext-zlib": "Allows using compression for response bodies." + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php" + ], + "psr-4": { + "Amp\\Http\\Client\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@gmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "An advanced async HTTP client library for PHP, enabling efficient, non-blocking, and concurrent requests and responses.", + "homepage": "https://amphp.org/http-client", + "keywords": [ + "async", + "client", + "concurrent", + "http", + "non-blocking", + "rest" + ], + "support": { + "issues": "https://github.com/amphp/http-client/issues", + "source": "https://github.com/amphp/http-client/tree/v5.3.4" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-08-16T20:41:23+00:00" + }, + { + "name": "amphp/parser", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/parser.git", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parser/zipball/3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "shasum": "" + }, + "require": { + "php": ">=7.4" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Parser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A generator parser to make streaming parsers simple.", + "homepage": "https://github.com/amphp/parser", + "keywords": [ + "async", + "non-blocking", + "parser", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/parser/issues", + "source": "https://github.com/amphp/parser/tree/v1.1.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-03-21T19:16:53+00:00" + }, + { + "name": "amphp/pipeline", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/pipeline.git", + "reference": "7b52598c2e9105ebcddf247fc523161581930367" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/7b52598c2e9105ebcddf247fc523161581930367", + "reference": "7b52598c2e9105ebcddf247fc523161581930367", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "php": ">=8.1", + "revolt/event-loop": "^1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.18" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Pipeline\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Asynchronous iterators and operators.", + "homepage": "https://amphp.org/pipeline", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "iterator", + "non-blocking" + ], + "support": { + "issues": "https://github.com/amphp/pipeline/issues", + "source": "https://github.com/amphp/pipeline/tree/v1.2.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-03-16T16:33:53+00:00" + }, + { + "name": "amphp/process", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/process.git", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/process/zipball/52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Process\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A fiber-aware process manager based on Amp and Revolt.", + "homepage": "https://amphp.org/process", + "support": { + "issues": "https://github.com/amphp/process/issues", + "source": "https://github.com/amphp/process/tree/v2.0.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T03:13:44+00:00" + }, + { + "name": "amphp/serialization", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/serialization.git", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "phpunit/phpunit": "^9 || ^8 || ^7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Serialization\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Serialization tools for IPC and data storage in PHP.", + "homepage": "https://github.com/amphp/serialization", + "keywords": [ + "async", + "asynchronous", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/amphp/serialization/issues", + "source": "https://github.com/amphp/serialization/tree/master" + }, + "time": "2020-03-25T21:39:07+00:00" + }, + { + "name": "amphp/socket", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/socket.git", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/socket/zipball/58e0422221825b79681b72c50c47a930be7bf1e1", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/dns": "^2", + "ext-openssl": "*", + "kelunik/certificate": "^1.1", + "league/uri": "^6.5 | ^7", + "league/uri-interfaces": "^2.3 | ^7", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "amphp/process": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php", + "src/SocketAddress/functions.php" + ], + "psr-4": { + "Amp\\Socket\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@gmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Non-blocking socket connection / server implementations based on Amp and Revolt.", + "homepage": "https://github.com/amphp/socket", + "keywords": [ + "amp", + "async", + "encryption", + "non-blocking", + "sockets", + "tcp", + "tls" + ], + "support": { + "issues": "https://github.com/amphp/socket/issues", + "source": "https://github.com/amphp/socket/tree/v2.3.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-21T14:33:03+00:00" + }, + { + "name": "amphp/sync", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/sync.git", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/sync/zipball/217097b785130d77cfcc58ff583cf26cd1770bf1", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Sync\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Non-blocking synchronization primitives for PHP based on Amp and Revolt.", + "homepage": "https://github.com/amphp/sync", + "keywords": [ + "async", + "asynchronous", + "mutex", + "semaphore", + "synchronization" + ], + "support": { + "issues": "https://github.com/amphp/sync/issues", + "source": "https://github.com/amphp/sync/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-08-03T19:31:26+00:00" + }, + { + "name": "api-platform/doctrine-common", + "version": "v4.2.11", + "source": { + "type": "git", + "url": "https://github.com/api-platform/doctrine-common.git", + "reference": "281f2ef1433253ec63a4f845622639665c1d68c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/doctrine-common/zipball/281f2ef1433253ec63a4f845622639665c1d68c5", + "reference": "281f2ef1433253ec63a4f845622639665c1d68c5", + "shasum": "" + }, + "require": { + "api-platform/metadata": "^4.2", + "api-platform/state": "^4.2.4", + "doctrine/collections": "^2.1", + "doctrine/common": "^3.2.2", + "doctrine/persistence": "^3.2 || ^4.0", + "php": ">=8.2" + }, + "conflict": { + "doctrine/persistence": "<1.3" + }, + "require-dev": { + "doctrine/mongodb-odm": "^2.10", + "doctrine/orm": "^2.17 || ^3.0", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", + "symfony/type-info": "^7.3 || ^8.0" + }, + "suggest": { + "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": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0 || ^8.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\\Doctrine\\Common\\": "" + } + }, + "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": "Common files used by api-platform/doctrine-orm and api-platform/doctrine-odm", + "homepage": "https://api-platform.com", + "keywords": [ + "doctrine", + "graphql", + "odm", + "orm", + "rest" + ], + "support": { + "source": "https://github.com/api-platform/doctrine-common/tree/v4.2.11" + }, + "time": "2025-12-04T14:20:26+00:00" + }, + { + "name": "api-platform/doctrine-orm", + "version": "v4.2.11", + "source": { + "type": "git", + "url": "https://github.com/api-platform/doctrine-orm.git", + "reference": "6ea550f2db2db04979aefd654c115ecd6f897039" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/doctrine-orm/zipball/6ea550f2db2db04979aefd654c115ecd6f897039", + "reference": "6ea550f2db2db04979aefd654c115ecd6f897039", + "shasum": "" + }, + "require": { + "api-platform/doctrine-common": "^4.2.9", + "api-platform/metadata": "^4.2", + "api-platform/state": "^4.2.4", + "doctrine/orm": "^2.17 || ^3.0", + "php": ">=8.2" + }, + "require-dev": { + "doctrine/doctrine-bundle": "^2.11 || ^3.1", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", + "ramsey/uuid": "^4.7", + "ramsey/uuid-doctrine": "^2.0", + "symfony/cache": "^6.4 || ^7.0 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/property-access": "^6.4 || ^7.0 || ^8.0", + "symfony/property-info": "^6.4 || ^7.1 || ^8.0", + "symfony/serializer": "^6.4 || ^7.0 || ^8.0", + "symfony/type-info": "^7.3 || ^8.0", + "symfony/uid": "^6.4 || ^7.0 || ^8.0", + "symfony/validator": "^6.4.11 || ^7.0 || ^8.0", + "symfony/yaml": "^6.4 || ^7.0 || ^8.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0 || ^8.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\\Doctrine\\Orm\\": "" + } + }, + "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": "Doctrine ORM bridge", + "homepage": "https://api-platform.com", + "keywords": [ + "api", + "doctrine", + "graphql", + "orm", + "rest" + ], + "support": { + "source": "https://github.com/api-platform/doctrine-orm/tree/v4.2.11" + }, + "time": "2025-12-04T14:20:26+00:00" + }, + { + "name": "api-platform/documentation", + "version": "v4.2.11", + "source": { + "type": "git", + "url": "https://github.com/api-platform/documentation.git", + "reference": "8910f2a0aa7910ed807f128510553b24152e5ea5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/documentation/zipball/8910f2a0aa7910ed807f128510553b24152e5ea5", + "reference": "8910f2a0aa7910ed807f128510553b24152e5ea5", + "shasum": "" + }, + "require": { + "api-platform/metadata": "^4.2", + "php": ">=8.2" + }, + "require-dev": { + "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 || ^8.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\\Documentation\\": "" + } + }, + "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 documentation controller.", + "support": { + "source": "https://github.com/api-platform/documentation/tree/v4.2.11" + }, + "time": "2025-11-30T12:55:42+00:00" + }, + { + "name": "api-platform/http-cache", + "version": "v4.2.11", + "source": { + "type": "git", + "url": "https://github.com/api-platform/http-cache.git", + "reference": "4bb2eab81407f493f54f51be7aa1918f362c14b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/http-cache/zipball/4bb2eab81407f493f54f51be7aa1918f362c14b5", + "reference": "4bb2eab81407f493f54f51be7aa1918f362c14b5", + "shasum": "" + }, + "require": { + "api-platform/metadata": "^4.2", + "api-platform/state": "^4.2.4", + "php": ">=8.2", + "symfony/http-foundation": "^6.4.14 || ^7.0 || ^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.0 || ^7.0 || ^8.0", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", + "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0", + "symfony/http-client": "^6.4 || ^7.0 || ^8.0", + "symfony/type-info": "^7.3 || ^8.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0 || ^8.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\\HttpCache\\": "" + }, + "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/comunnity/contributors" + } + ], + "description": "API Platform HttpCache component", + "homepage": "https://api-platform.com", + "keywords": [ + "api", + "cache", + "http", + "rest" + ], + "support": { + "source": "https://github.com/api-platform/http-cache/tree/v4.2.11" + }, + "time": "2025-11-30T12:55:42+00:00" + }, + { + "name": "api-platform/hydra", + "version": "v4.2.11", + "source": { + "type": "git", + "url": "https://github.com/api-platform/hydra.git", + "reference": "80491f175647d0a63eb96035b2468fc1c2a76c37" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/hydra/zipball/80491f175647d0a63eb96035b2468fc1c2a76c37", + "reference": "80491f175647d0a63eb96035b2468fc1c2a76c37", + "shasum": "" + }, + "require": { + "api-platform/documentation": "^4.2", + "api-platform/json-schema": "^4.2", + "api-platform/jsonld": "^4.2", + "api-platform/metadata": "^4.2", + "api-platform/serializer": "^4.2.4", + "api-platform/state": "^4.2.4", + "php": ">=8.2", + "symfony/type-info": "^7.3 || ^8.0", + "symfony/web-link": "^6.4 || ^7.1 || ^8.0" + }, + "require-dev": { + "api-platform/doctrine-common": "^4.2", + "api-platform/doctrine-odm": "^4.2", + "api-platform/doctrine-orm": "^4.2", + "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 || ^8.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\\Hydra\\": "" + }, + "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 Hydra support", + "homepage": "https://api-platform.com", + "keywords": [ + "Hydra", + "JSON-LD", + "api", + "graphql", + "jsonapi", + "rest" + ], + "support": { + "source": "https://github.com/api-platform/hydra/tree/v4.2.11" + }, + "time": "2025-12-18T14:34:13+00:00" + }, + { + "name": "api-platform/json-api", + "version": "v4.2.11", + "source": { + "type": "git", + "url": "https://github.com/api-platform/json-api.git", + "reference": "2bb93263f900401c41476b93bcf03c386c9500d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/json-api/zipball/2bb93263f900401c41476b93bcf03c386c9500d4", + "reference": "2bb93263f900401c41476b93bcf03c386c9500d4", + "shasum": "" + }, + "require": { + "api-platform/documentation": "^4.2", + "api-platform/json-schema": "^4.2", + "api-platform/metadata": "^4.2", + "api-platform/serializer": "^4.2.4", + "api-platform/state": "^4.2.4", + "php": ">=8.2", + "symfony/error-handler": "^6.4 || ^7.0 || ^8.0", + "symfony/http-foundation": "^6.4.14 || ^7.0 || ^8.0", + "symfony/type-info": "^7.3 || ^8.0" + }, + "require-dev": { + "phpspec/prophecy": "^1.19", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", + "symfony/type-info": "^7.3 || ^8.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0 || ^8.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.11" + }, + "time": "2025-12-19T14:13:59+00:00" + }, + { + "name": "api-platform/json-schema", + "version": "v4.2.11", + "source": { + "type": "git", + "url": "https://github.com/api-platform/json-schema.git", + "reference": "6a5f901a744018e48b8b94bbf798cd4f617cfcde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/json-schema/zipball/6a5f901a744018e48b8b94bbf798cd4f617cfcde", + "reference": "6a5f901a744018e48b8b94bbf798cd4f617cfcde", + "shasum": "" + }, + "require": { + "api-platform/metadata": "^4.2", + "php": ">=8.2", + "symfony/console": "^6.4 || ^7.0 || ^8.0", + "symfony/property-info": "^6.4 || ^7.1 || ^8.0", + "symfony/serializer": "^6.4 || ^7.0 || ^8.0", + "symfony/type-info": "^7.3 || ^8.0", + "symfony/uid": "^6.4 || ^7.0 || ^8.0" + }, + "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 || ^8.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.11" + }, + "time": "2025-12-02T13:32:19+00:00" + }, + { + "name": "api-platform/jsonld", + "version": "v4.2.11", + "source": { + "type": "git", + "url": "https://github.com/api-platform/jsonld.git", + "reference": "3889b185376198a182d2527c48ec0b29b604505f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/jsonld/zipball/3889b185376198a182d2527c48ec0b29b604505f", + "reference": "3889b185376198a182d2527c48ec0b29b604505f", + "shasum": "" + }, + "require": { + "api-platform/metadata": "^4.2", + "api-platform/serializer": "^4.2.4", + "api-platform/state": "^4.2.4", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "11.5.x-dev", + "symfony/type-info": "^7.3 || ^8.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0 || ^8.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.11" + }, + "time": "2025-12-18T14:34:13+00:00" + }, + { + "name": "api-platform/metadata", + "version": "v4.2.11", + "source": { + "type": "git", + "url": "https://github.com/api-platform/metadata.git", + "reference": "533774ca55a4f2be9da72da344d5e3e2982fbc86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/metadata/zipball/533774ca55a4f2be9da72da344d5e3e2982fbc86", + "reference": "533774ca55a4f2be9da72da344d5e3e2982fbc86", + "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 || ^8.0", + "symfony/string": "^6.4 || ^7.0 || ^8.0", + "symfony/type-info": "^7.3 || ^8.0" + }, + "require-dev": { + "api-platform/json-schema": "^4.2", + "api-platform/openapi": "^4.2", + "api-platform/state": "^4.2.4", + "phpspec/prophecy-phpunit": "^2.2", + "phpstan/phpdoc-parser": "^1.29 || ^2.0", + "phpunit/phpunit": "11.5.x-dev", + "symfony/config": "^6.4 || ^7.0 || ^8.0", + "symfony/routing": "^6.4 || ^7.0 || ^8.0", + "symfony/var-dumper": "^6.4 || ^7.0 || ^8.0", + "symfony/web-link": "^6.4 || ^7.1 || ^8.0", + "symfony/yaml": "^6.4 || ^7.0 || ^8.0" + }, + "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 || ^8.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.11" + }, + "time": "2025-12-18T14:34:13+00:00" + }, + { + "name": "api-platform/openapi", + "version": "v4.2.11", + "source": { + "type": "git", + "url": "https://github.com/api-platform/openapi.git", + "reference": "ea49d6d7170f8ecc1c239e7769708628183096b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/openapi/zipball/ea49d6d7170f8ecc1c239e7769708628183096b8", + "reference": "ea49d6d7170f8ecc1c239e7769708628183096b8", + "shasum": "" + }, + "require": { + "api-platform/json-schema": "^4.2", + "api-platform/metadata": "^4.2", + "api-platform/state": "^4.2.4", + "php": ">=8.2", + "symfony/console": "^6.4 || ^7.0 || ^8.0", + "symfony/filesystem": "^6.4 || ^7.0 || ^8.0", + "symfony/property-access": "^6.4 || ^7.0 || ^8.0", + "symfony/serializer": "^6.4 || ^7.0 || ^8.0", + "symfony/type-info": "^7.3 || ^8.0" + }, + "require-dev": { + "api-platform/doctrine-common": "^4.2", + "api-platform/doctrine-odm": "^4.2", + "api-platform/doctrine-orm": "^4.2", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", + "symfony/type-info": "^7.3 || ^8.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0 || ^8.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.11" + }, + "time": "2025-11-30T12:55:42+00:00" + }, + { + "name": "api-platform/serializer", + "version": "v4.2.11", + "source": { + "type": "git", + "url": "https://github.com/api-platform/serializer.git", + "reference": "a511d4ba522c3ebbd78c9e1f05e0918978d72a43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/serializer/zipball/a511d4ba522c3ebbd78c9e1f05e0918978d72a43", + "reference": "a511d4ba522c3ebbd78c9e1f05e0918978d72a43", + "shasum": "" + }, + "require": { + "api-platform/metadata": "^4.2", + "api-platform/state": "^4.2.4", + "php": ">=8.2", + "symfony/property-access": "^6.4 || ^7.0 || ^8.0", + "symfony/property-info": "^6.4 || ^7.1 || ^8.0", + "symfony/serializer": "^6.4 || ^7.0 || ^8.0", + "symfony/validator": "^6.4.11 || ^7.0 || ^8.0" + }, + "require-dev": { + "api-platform/doctrine-common": "^4.2", + "api-platform/doctrine-odm": "^4.2", + "api-platform/doctrine-orm": "^4.2", + "api-platform/json-schema": "^4.2", + "api-platform/openapi": "^4.2", + "doctrine/collections": "^2.1", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", + "symfony/mercure-bundle": "*", + "symfony/type-info": "^7.3 || ^8.0", + "symfony/var-dumper": "^6.4 || ^7.0 || ^8.0", + "symfony/yaml": "^6.4 || ^7.0 || ^8.0" + }, + "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 || ^8.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.11" + }, + "time": "2025-12-18T14:36:58+00:00" + }, + { + "name": "api-platform/state", + "version": "v4.2.11", + "source": { + "type": "git", + "url": "https://github.com/api-platform/state.git", + "reference": "b9644669f953a76742c9f49d571ff42c68e581d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/state/zipball/b9644669f953a76742c9f49d571ff42c68e581d1", + "reference": "b9644669f953a76742c9f49d571ff42c68e581d1", + "shasum": "" + }, + "require": { + "api-platform/metadata": "^4.2.3", + "php": ">=8.2", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^3.1", + "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0", + "symfony/serializer": "^6.4 || ^7.0 || ^8.0", + "symfony/translation-contracts": "^3.0" + }, + "require-dev": { + "api-platform/serializer": "^4.2.4", + "api-platform/validator": "^4.2.4", + "phpunit/phpunit": "11.5.x-dev", + "symfony/http-foundation": "^6.4.14 || ^7.0 || ^8.0", + "symfony/object-mapper": "^7.4 || ^8.0", + "symfony/type-info": "^7.4 || ^8.0", + "symfony/web-link": "^6.4 || ^7.1 || ^8.0", + "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 || ^8.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.11" + }, + "time": "2025-12-17T15:10:17+00:00" + }, + { + "name": "api-platform/symfony", + "version": "v4.2.11", + "source": { + "type": "git", + "url": "https://github.com/api-platform/symfony.git", + "reference": "ab93a0043558beeb7ccd7f2c97304565d4872bb3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/symfony/zipball/ab93a0043558beeb7ccd7f2c97304565d4872bb3", + "reference": "ab93a0043558beeb7ccd7f2c97304565d4872bb3", + "shasum": "" + }, + "require": { + "api-platform/documentation": "^4.2.3", + "api-platform/http-cache": "^4.2.3", + "api-platform/hydra": "^4.2.3", + "api-platform/json-schema": "^4.2.3", + "api-platform/jsonld": "^4.2.3", + "api-platform/metadata": "^4.2.3", + "api-platform/openapi": "^4.2.3", + "api-platform/serializer": "^4.2.4", + "api-platform/state": "^4.2.4", + "api-platform/validator": "^4.2.3", + "php": ">=8.2", + "symfony/asset": "^6.4 || ^7.0 || ^8.0", + "symfony/finder": "^6.4 || ^7.0 || ^8.0", + "symfony/property-access": "^6.4 || ^7.0 || ^8.0", + "symfony/property-info": "^6.4 || ^7.0 || ^8.0", + "symfony/security-core": "^6.4 || ^7.0 || ^8.0", + "symfony/serializer": "^6.4 || ^7.0 || ^8.0", + "willdurand/negotiation": "^3.1" + }, + "require-dev": { + "api-platform/doctrine-common": "^4.2.3", + "api-platform/doctrine-odm": "^4.2.3", + "api-platform/doctrine-orm": "^4.2.3", + "api-platform/elasticsearch": "^4.2.3", + "api-platform/graphql": "^4.2.3", + "api-platform/hal": "^4.2.3", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", + "symfony/expression-language": "^6.4 || ^7.0 || ^8.0", + "symfony/intl": "^6.4 || ^7.0 || ^8.0", + "symfony/mercure-bundle": "*", + "symfony/object-mapper": "^7.0 || ^8.0", + "symfony/routing": "^6.4 || ^7.0 || ^8.0", + "symfony/type-info": "^7.3 || ^8.0", + "symfony/validator": "^6.4.11 || ^7.0 || ^8.0", + "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/json-api": "to support the JSON-API 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 || ^8.0" + }, + "branch-alias": { + "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.11" + }, + "time": "2025-12-18T14:34:13+00:00" + }, + { + "name": "api-platform/validator", + "version": "v4.2.11", + "source": { + "type": "git", + "url": "https://github.com/api-platform/validator.git", + "reference": "850035ba6165e2452c1777bee2272205bf8c771e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/validator/zipball/850035ba6165e2452c1777bee2272205bf8c771e", + "reference": "850035ba6165e2452c1777bee2272205bf8c771e", + "shasum": "" + }, + "require": { + "api-platform/metadata": "^4.2", + "php": ">=8.2", + "symfony/http-kernel": "^6.4 || ^7.1 || ^8.0", + "symfony/serializer": "^6.4 || ^7.1 || ^8.0", + "symfony/type-info": "^7.3 || ^8.0", + "symfony/validator": "^6.4.11 || ^7.1 || ^8.0", + "symfony/web-link": "^6.4 || ^7.1 || ^8.0" + }, + "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 || ^8.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.11" + }, + "time": "2025-12-04T14:20:26+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": { @@ -25,7 +2217,7 @@ "ext-json": "*", "ext-mbstring": "*", "ext-simplexml": "*", - "php": "^7.0 || ^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", @@ -69,33 +2261,38 @@ ], "support": { "issues": "https://github.com/beberlei/assert/issues", - "source": "https://github.com/beberlei/assert/tree/v3.3.2" + "source": "https://github.com/beberlei/assert/tree/v3.3.3" }, - "time": "2021-12-16T21:41:27+00:00" + "time": "2024-07-15T13:18:35+00:00" }, { "name": "beberlei/doctrineextensions", - "version": "v1.3.0", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/beberlei/DoctrineExtensions.git", - "reference": "008f162f191584a6c37c03a803f718802ba9dd9a" + "reference": "281f1650641c2f438b0a54d8eaa7ba50ac7e3eb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beberlei/DoctrineExtensions/zipball/008f162f191584a6c37c03a803f718802ba9dd9a", - "reference": "008f162f191584a6c37c03a803f718802ba9dd9a", + "url": "https://api.github.com/repos/beberlei/DoctrineExtensions/zipball/281f1650641c2f438b0a54d8eaa7ba50ac7e3eb6", + "reference": "281f1650641c2f438b0a54d8eaa7ba50ac7e3eb6", "shasum": "" }, "require": { - "doctrine/orm": "^2.7", + "doctrine/orm": "^2.19 || ^3.0", "php": "^7.2 || ^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.14", - "nesbot/carbon": "*", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "symfony/yaml": "^4.2 || ^5.0", + "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" }, @@ -126,31 +2323,31 @@ "orm" ], "support": { - "source": "https://github.com/beberlei/DoctrineExtensions/tree/v1.3.0" + "source": "https://github.com/beberlei/DoctrineExtensions/tree/v1.5.0" }, - "time": "2020-11-29T07:37:23+00:00" + "time": "2024-03-03T17:55:15+00:00" }, { "name": "brick/math", - "version": "0.11.0", + "version": "0.13.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478" + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/0ad82ce168c82ba30d1c01ec86116ab52f589478", - "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478", + "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", "shasum": "" }, "require": { - "php": "^8.0" + "php": "^8.1" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^9.0", - "vimeo/psalm": "5.0.0" + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "6.8.8" }, "type": "library", "autoload": { @@ -170,12 +2367,17 @@ "arithmetic", "bigdecimal", "bignum", + "bignumber", "brick", - "math" + "decimal", + "integer", + "math", + "mathematics", + "rational" ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.11.0" + "source": "https://github.com/brick/math/tree/0.13.1" }, "funding": [ { @@ -183,32 +2385,32 @@ "type": "github" } ], - "time": "2023-01-15T23:15:59+00:00" + "time": "2025-03-29T13:50:30+00:00" }, { "name": "composer/ca-bundle", - "version": "1.3.6", + "version": "1.5.10", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "90d087e988ff194065333d16bc5cf649872d9cdb" + "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/90d087e988ff194065333d16bc5cf649872d9cdb", - "reference": "90d087e988ff194065333d16bc5cf649872d9cdb", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/961a5e4056dd2e4a2eedcac7576075947c28bf63", + "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", - "psr/log": "^1.0", - "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + "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": { @@ -243,7 +2445,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.3.6" + "source": "https://github.com/composer/ca-bundle/tree/1.5.10" }, "funding": [ { @@ -253,13 +2455,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2023-06-06T12:02:59+00:00" + "time": "2025-12-08T15:06:51+00:00" }, { "name": "composer/package-versions-deprecated", @@ -335,113 +2533,44 @@ "time": "2022-01-17T14:14:24+00:00" }, { - "name": "doctrine/annotations", - "version": "1.14.3", + "name": "composer/pcre", + "version": "3.3.2", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af" + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", - "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "shasum": "" }, "require": { - "doctrine/lexer": "^1 || ^2", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "~1.4.10 || ^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6", - "vimeo/psalm": "^4.10" - }, - "suggest": { - "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.14.3" - }, - "time": "2023-02-01T09:20:38+00:00" - }, - { - "name": "doctrine/cache", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", - "shasum": "" - }, - "require": { - "php": "~7.1 || ^8.0" + "php": "^7.4 || ^8.0" }, "conflict": { - "doctrine/common": ">2.2,<2.4" + "phpstan/phpstan": "<1.11.10" }, "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" + "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": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + "Composer\\Pcre\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -450,84 +2579,182 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "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": "PCRE wrapping library that offers type-safe preg_* replacements.", "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" + "PCRE", + "preg", + "regex", + "regular expression" ], "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.2.0" + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" }, "funding": [ { - "url": "https://www.doctrine-project.org/sponsorship.html", + "url": "https://packagist.com", "type": "custom" }, { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" + "url": "https://github.com/composer", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], - "time": "2022-05-20T20:07:39+00:00" + "time": "2024-11-12T16:29:46+00:00" }, { - "name": "doctrine/collections", - "version": "2.1.3", + "name": "daverandom/libdns", + "version": "v2.1.0", "source": { "type": "git", - "url": "https://github.com/doctrine/collections.git", - "reference": "3023e150f90a38843856147b58190aa8b46cc155" + "url": "https://github.com/DaveRandom/LibDNS.git", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/3023e150f90a38843856147b58190aa8b46cc155", - "reference": "3023e150f90a38843856147b58190aa8b46cc155", + "url": "https://api.github.com/repos/DaveRandom/LibDNS/zipball/b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "Required for IDN support" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "LibDNS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "DNS protocol implementation written in pure PHP", + "keywords": [ + "dns" + ], + "support": { + "issues": "https://github.com/DaveRandom/LibDNS/issues", + "source": "https://github.com/DaveRandom/LibDNS/tree/v2.1.0" + }, + "time": "2024-04-12T12:12:48+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "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.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "9acfeea2e8666536edff3d77c531261c63680160" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/9acfeea2e8666536edff3d77c531261c63680160", + "reference": "9acfeea2e8666536edff3d77c531261c63680160", "shasum": "" }, "require": { "doctrine/deprecations": "^1", - "php": "^8.1" + "php": "^8.1", + "symfony/polyfill-php84": "^1.30" }, "require-dev": { - "doctrine/coding-standard": "^10.0", + "doctrine/coding-standard": "^14", "ext-json": "*", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^5.11" + "phpstan/phpstan": "^2.1.30", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpunit/phpunit": "^10.5.58 || ^11.5.42 || ^12.4" }, "type": "library", "autoload": { @@ -571,7 +2798,7 @@ ], "support": { "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/2.1.3" + "source": "https://github.com/doctrine/collections/tree/2.4.0" }, "funding": [ { @@ -587,24 +2814,24 @@ "type": "tidelift" } ], - "time": "2023-07-06T15:15:36+00:00" + "time": "2025-10-25T09:18:13+00:00" }, { "name": "doctrine/common", - "version": "3.4.3", + "version": "3.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/common.git", - "reference": "8b5e5650391f851ed58910b3e3d48a71062eeced" + "reference": "d9ea4a54ca2586db781f0265d36bea731ac66ec5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/8b5e5650391f851ed58910b3e3d48a71062eeced", - "reference": "8b5e5650391f851ed58910b3e3d48a71062eeced", + "url": "https://api.github.com/repos/doctrine/common/zipball/d9ea4a54ca2586db781f0265d36bea731ac66ec5", + "reference": "d9ea4a54ca2586db781f0265d36bea731ac66ec5", "shasum": "" }, "require": { - "doctrine/persistence": "^2.0 || ^3.0", + "doctrine/persistence": "^2.0 || ^3.0 || ^4.0", "php": "^7.1 || ^8.0" }, "require-dev": { @@ -662,7 +2889,7 @@ ], "support": { "issues": "https://github.com/doctrine/common/issues", - "source": "https://github.com/doctrine/common/tree/3.4.3" + "source": "https://github.com/doctrine/common/tree/3.5.0" }, "funding": [ { @@ -678,42 +2905,43 @@ "type": "tidelift" } ], - "time": "2022-10-09T11:47:59+00:00" + "time": "2025-01-01T22:12:03+00:00" }, { "name": "doctrine/data-fixtures", - "version": "1.6.7", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/data-fixtures.git", - "reference": "ae4e845decbe177348fdbecd04331f4fb96aa301" + "reference": "7a615ba135e45d67674bb623d90f34f6c7b6bd97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/ae4e845decbe177348fdbecd04331f4fb96aa301", - "reference": "ae4e845decbe177348fdbecd04331f4fb96aa301", + "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/7a615ba135e45d67674bb623d90f34f6c7b6bd97", + "reference": "7a615ba135e45d67674bb623d90f34f6c7b6bd97", "shasum": "" }, "require": { - "doctrine/deprecations": "^0.5.3 || ^1.0", - "doctrine/persistence": "^1.3.3 || ^2.0 || ^3.0", - "php": "^7.2 || ^8.0" + "doctrine/persistence": "^3.1 || ^4.0", + "php": "^8.1", + "psr/log": "^1.1 || ^2 || ^3" }, "conflict": { - "doctrine/dbal": "<2.13", - "doctrine/orm": "<2.14", + "doctrine/dbal": "<3.5 || >=5", + "doctrine/orm": "<2.14 || >=4", "doctrine/phpcr-odm": "<1.3.0" }, "require-dev": { - "doctrine/coding-standard": "^11.0", - "doctrine/dbal": "^2.13 || ^3.0", + "doctrine/coding-standard": "^14", + "doctrine/dbal": "^3.5 || ^4", "doctrine/mongodb-odm": "^1.3.0 || ^2.0.0", - "doctrine/orm": "^2.14", + "doctrine/orm": "^2.14 || ^3", "ext-sqlite3": "*", - "phpstan/phpstan": "^1.5", - "phpunit/phpunit": "^8.5 || ^9.5 || ^10.0", - "symfony/cache": "^5.0 || ^6.0", - "vimeo/psalm": "^4.10 || ^5.9" + "fig/log-test": "^1", + "phpstan/phpstan": "2.1.31", + "phpunit/phpunit": "10.5.45 || 12.4.0", + "symfony/cache": "^6.4 || ^7", + "symfony/var-exporter": "^6.4 || ^7" }, "suggest": { "alcaeus/mongo-php-adapter": "For using MongoDB ODM 1.3 with PHP 7 (deprecated)", @@ -744,7 +2972,7 @@ ], "support": { "issues": "https://github.com/doctrine/data-fixtures/issues", - "source": "https://github.com/doctrine/data-fixtures/tree/1.6.7" + "source": "https://github.com/doctrine/data-fixtures/tree/2.2.0" }, "funding": [ { @@ -760,51 +2988,44 @@ "type": "tidelift" } ], - "time": "2023-08-17T21:15:33+00:00" + "time": "2025-10-17T20:06:20+00:00" }, { "name": "doctrine/dbal", - "version": "3.6.6", + "version": "4.4.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "63646ffd71d1676d2f747f871be31b7e921c7864" + "reference": "3d544473fb93f5c25b483ea4f4ce99f8c4d9d44c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/63646ffd71d1676d2f747f871be31b7e921c7864", - "reference": "63646ffd71d1676d2f747f871be31b7e921c7864", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/3d544473fb93f5c25b483ea4f4ce99f8c4d9d44c", + "reference": "3d544473fb93f5c25b483ea4f4ce99f8c4d9d44c", "shasum": "" }, "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", - "doctrine/deprecations": "^0.5.3|^1", - "doctrine/event-manager": "^1|^2", - "php": "^7.4 || ^8.0", + "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.1", - "phpstan/phpstan": "1.10.29", - "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.9", - "psalm/plugin-phpunit": "0.18.4", - "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.7.2", - "symfony/cache": "^5.4|^6.0", - "symfony/console": "^4.4|^5.4|^6.0", - "vimeo/psalm": "4.30.0" + "jetbrains/phpstorm-stubs": "2023.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|^8.0", + "symfony/console": "^5.4|^6.3|^7.0|^8.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." }, - "bin": [ - "bin/doctrine-dbal" - ], "type": "library", "autoload": { "psr-4": { @@ -857,7 +3078,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.6" + "source": "https://github.com/doctrine/dbal/tree/4.4.1" }, "funding": [ { @@ -873,33 +3094,34 @@ "type": "tidelift" } ], - "time": "2023-08-17T05:38:17+00:00" + "time": "2025-12-04T10:11:03+00:00" }, { "name": "doctrine/deprecations", - "version": "v1.1.1", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3" + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", + "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", - "phpstan/phpstan": "1.4.10 || 1.10.15", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "0.18.4", - "psr/log": "^1 || ^2 || ^3", - "vimeo/psalm": "4.30.0 || 5.12.0" + "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.6 || ^10.5 || ^11.5 || ^12", + "psr/log": "^1 || ^2 || ^3" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -907,7 +3129,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + "Doctrine\\Deprecations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -918,64 +3140,69 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.1.1" + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" }, - "time": "2023-06-03T09:27:29+00:00" + "time": "2025-04-07T20:06:18+00:00" }, { "name": "doctrine/doctrine-bundle", - "version": "2.10.2", + "version": "2.18.2", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "f28b1f78de3a2938ff05cfe751233097624cc756" + "reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/f28b1f78de3a2938ff05cfe751233097624cc756", - "reference": "f28b1f78de3a2938ff05cfe751233097624cc756", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/0ff098b29b8b3c68307c8987dcaed7fd829c6546", + "reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546", "shasum": "" }, "require": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/dbal": "^3.6.0", - "doctrine/persistence": "^2.2 || ^3", + "doctrine/dbal": "^3.7.0 || ^4.0", + "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", - "symfony/config": "^5.4 || ^6.0", - "symfony/console": "^5.4 || ^6.0", - "symfony/dependency-injection": "^5.4 || ^6.0", - "symfony/deprecation-contracts": "^2.1 || ^3", - "symfony/doctrine-bridge": "^5.4.19 || ^6.0.7", - "symfony/framework-bundle": "^5.4 || ^6.0", - "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/orm": "<2.11 || >=3.0", - "twig/twig": "<1.34 || >=2.0 <2.4" + "doctrine/cache": "< 1.11", + "doctrine/orm": "<2.17 || >=4.0", + "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": "^9.0", - "doctrine/deprecations": "^1.0", - "doctrine/orm": "^2.11 || ^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 || ^10.0", - "psalm/plugin-phpunit": "^0.18.4", - "psalm/plugin-symfony": "^4", + "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", - "symfony/property-info": "^5.4 || ^6.0", - "symfony/proxy-manager-bridge": "^5.4 || ^6.0", - "symfony/security-bundle": "^5.4 || ^6.0", - "symfony/twig-bridge": "^5.4 || ^6.0", - "symfony/validator": "^5.4 || ^6.0", - "symfony/web-profiler-bundle": "^5.4 || ^6.0", - "symfony/yaml": "^5.4 || ^6.0", - "twig/twig": "^1.34 || ^2.12 || ^3.0", - "vimeo/psalm": "^4.30" + "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.", @@ -985,7 +3212,7 @@ "type": "symfony-bundle", "autoload": { "psr-4": { - "Doctrine\\Bundle\\DoctrineBundle\\": "" + "Doctrine\\Bundle\\DoctrineBundle\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1020,7 +3247,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.10.2" + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.2" }, "funding": [ { @@ -1036,47 +3263,47 @@ "type": "tidelift" } ], - "time": "2023-08-06T09:31:40+00:00" + "time": "2025-12-20T21:35:32+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", - "version": "3.2.4", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", - "reference": "94e6b0fe1a50901d52f59dbb9b4b0737718b2c1e" + "reference": "1e380c6dd8ac8488217f39cff6b77e367f1a644b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/94e6b0fe1a50901d52f59dbb9b4b0737718b2c1e", - "reference": "94e6b0fe1a50901d52f59dbb9b4b0737718b2c1e", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/1e380c6dd8ac8488217f39cff6b77e367f1a644b", + "reference": "1e380c6dd8ac8488217f39cff6b77e367f1a644b", "shasum": "" }, "require": { - "doctrine/doctrine-bundle": "~1.0|~2.0", + "doctrine/doctrine-bundle": "^2.4 || ^3.0", "doctrine/migrations": "^3.2", - "php": "^7.2|^8.0", - "symfony/framework-bundle": "~3.4|~4.0|~5.0|~6.0" + "php": "^7.2 || ^8.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9", - "doctrine/orm": "^2.6", - "doctrine/persistence": "^1.3||^2.0", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-deprecation-rules": "^1", - "phpstan/phpstan-phpunit": "^1", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5|^9.5", - "vimeo/psalm": "^4.22" + "composer/semver": "^3.0", + "doctrine/coding-standard": "^12 || ^14", + "doctrine/orm": "^2.6 || ^3", + "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 || ^8", + "symfony/var-exporter": "^5.4 || ^6 || ^7 || ^8" }, "type": "symfony-bundle", "autoload": { "psr-4": { - "Doctrine\\Bundle\\MigrationsBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Doctrine\\Bundle\\MigrationsBundle\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1105,7 +3332,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", - "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.2.4" + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.7.0" }, "funding": [ { @@ -1121,20 +3348,20 @@ "type": "tidelift" } ], - "time": "2023-06-02T08:19:26+00:00" + "time": "2025-11-15T19:02:59+00:00" }, { "name": "doctrine/event-manager", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32" + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/750671534e0241a7c50ea5b43f67e23eb5c96f32", - "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", "shasum": "" }, "require": { @@ -1144,10 +3371,10 @@ "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^10", + "doctrine/coding-standard": "^12", "phpstan/phpstan": "^1.8.8", - "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^4.28" + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.24" }, "type": "library", "autoload": { @@ -1196,7 +3423,7 @@ ], "support": { "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/2.0.0" + "source": "https://github.com/doctrine/event-manager/tree/2.0.1" }, "funding": [ { @@ -1212,37 +3439,36 @@ "type": "tidelift" } ], - "time": "2022-10-12T20:59:15+00:00" + "time": "2024-05-22T20:47:39+00:00" }, { "name": "doctrine/inflector", - "version": "2.0.8", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff" + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/f9301a5b2fb1216b2b08f02ba04dc45423db6bff", - "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff", + "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/", @@ -1287,7 +3513,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.8" + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { @@ -1303,7 +3529,7 @@ "type": "tidelift" } ], - "time": "2023-06-16T13:40:37+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { "name": "doctrine/instantiator", @@ -1377,28 +3603,27 @@ }, { "name": "doctrine/lexer", - "version": "2.1.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", "shasum": "" }, "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^4.11 || ^5.0" + "vimeo/psalm": "^5.21" }, "type": "library", "autoload": { @@ -1435,7 +3660,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/2.1.0" + "source": "https://github.com/doctrine/lexer/tree/3.0.1" }, "funding": [ { @@ -1451,51 +3676,52 @@ "type": "tidelift" } ], - "time": "2022-12-14T08:49:07+00:00" + "time": "2024-02-05T11:56:58+00:00" }, { "name": "doctrine/migrations", - "version": "3.6.0", + "version": "3.9.5", "source": { "type": "git", "url": "https://github.com/doctrine/migrations.git", - "reference": "e542ad8bcd606d7a18d0875babb8a6d963c9c059" + "reference": "1b823afbc40f932dae8272574faee53f2755eac5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/e542ad8bcd606d7a18d0875babb8a6d963c9c059", - "reference": "e542ad8bcd606d7a18d0875babb8a6d963c9c059", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/1b823afbc40f932dae8272574faee53f2755eac5", + "reference": "1b823afbc40f932dae8272574faee53f2755eac5", "shasum": "" }, "require": { "composer-runtime-api": "^2", - "doctrine/dbal": "^3.5.1", + "doctrine/dbal": "^3.6 || ^4", "doctrine/deprecations": "^0.5.3 || ^1", "doctrine/event-manager": "^1.2 || ^2.0", "php": "^8.1", "psr/log": "^1.1.3 || ^2 || ^3", - "symfony/console": "^4.4.16 || ^5.4 || ^6.0", - "symfony/stopwatch": "^4.4 || ^5.4 || ^6.0", - "symfony/var-exporter": "^6.2" + "symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/var-exporter": "^6.2 || ^7.0 || ^8.0" }, "conflict": { - "doctrine/orm": "<2.12" + "doctrine/orm": "<2.12 || >=4" }, "require-dev": { - "doctrine/coding-standard": "^9", - "doctrine/orm": "^2.13", - "doctrine/persistence": "^2 || ^3", + "doctrine/coding-standard": "^14", + "doctrine/orm": "^2.13 || ^3", + "doctrine/persistence": "^2 || ^3 || ^4", "doctrine/sql-formatter": "^1.0", "ext-pdo_sqlite": "*", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-deprecation-rules": "^1", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.1", - "phpstan/phpstan-symfony": "^1.1", - "phpunit/phpunit": "^9.5.24", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "symfony/process": "^4.4 || ^5.4 || ^6.0", - "symfony/yaml": "^4.4 || ^5.4 || ^6.0" + "fig/log-test": "^1", + "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 || ^8.0", + "symfony/process": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "suggest": { "doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.", @@ -1507,7 +3733,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Migrations\\": "lib/Doctrine/Migrations" + "Doctrine\\Migrations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1537,7 +3763,7 @@ ], "support": { "issues": "https://github.com/doctrine/migrations/issues", - "source": "https://github.com/doctrine/migrations/tree/3.6.0" + "source": "https://github.com/doctrine/migrations/tree/3.9.5" }, "funding": [ { @@ -1553,69 +3779,56 @@ "type": "tidelift" } ], - "time": "2023-02-15T18:49:46+00:00" + "time": "2025-11-20T11:15:36+00:00" }, { "name": "doctrine/orm", - "version": "2.16.1", + "version": "3.6.0", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "597a63a86ca8c5f9d1ec2dc74fe3d1269d43434a" + "reference": "d4e9276e79602b1eb4c4029c6c999b0d93478e2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/597a63a86ca8c5f9d1ec2dc74fe3d1269d43434a", - "reference": "597a63a86ca8c5f9d1ec2dc74fe3d1269d43434a", + "url": "https://api.github.com/repos/doctrine/orm/zipball/d4e9276e79602b1eb4c4029c6c999b0d93478e2f", + "reference": "d4e9276e79602b1eb4c4029c6c999b0d93478e2f", "shasum": "" }, "require": { "composer-runtime-api": "^2", - "doctrine/cache": "^1.12.1 || ^2.1.1", - "doctrine/collections": "^1.5 || ^2.1", - "doctrine/common": "^3.0.3", - "doctrine/dbal": "^2.13.1 || ^3.2", + "doctrine/collections": "^2.2", + "doctrine/dbal": "^3.8.2 || ^4", "doctrine/deprecations": "^0.5.3 || ^1", "doctrine/event-manager": "^1.2 || ^2", "doctrine/inflector": "^1.4 || ^2.0", "doctrine/instantiator": "^1.3 || ^2", - "doctrine/lexer": "^2", - "doctrine/persistence": "^2.4 || ^3", + "doctrine/lexer": "^3", + "doctrine/persistence": "^3.3.1 || ^4", "ext-ctype": "*", - "php": "^7.1 || ^8.0", + "php": "^8.1", "psr/cache": "^1 || ^2 || ^3", - "symfony/console": "^4.2 || ^5.0 || ^6.0", - "symfony/polyfill-php72": "^1.23", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "doctrine/annotations": "<1.13 || >= 3.0" + "symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/var-exporter": "^6.3.9 || ^7.0 || ^8.0" }, "require-dev": { - "doctrine/annotations": "^1.13 || ^2", - "doctrine/coding-standard": "^9.0.2 || ^12.0", - "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/phpstan": "~1.4.10 || 1.10.28", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", + "doctrine/coding-standard": "^14.0", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "2.1.23", + "phpstan/phpstan-deprecation-rules": "^2", + "phpunit/phpunit": "^10.5.0 || ^11.5", "psr/log": "^1 || ^2 || ^3", - "squizlabs/php_codesniffer": "3.7.2", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6.2", - "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "vimeo/psalm": "4.30.0 || 5.14.1" + "symfony/cache": "^5.4 || ^6.2 || ^7.0 || ^8.0" }, "suggest": { "ext-dom": "Provides support for XSD validation for XML mapping files", - "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", - "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0" }, - "bin": [ - "bin/doctrine" - ], "type": "library", "autoload": { "psr-4": { - "Doctrine\\ORM\\": "lib/Doctrine/ORM" + "Doctrine\\ORM\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1652,42 +3865,37 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.16.1" + "source": "https://github.com/doctrine/orm/tree/3.6.0" }, - "time": "2023-08-09T13:05:08+00:00" + "time": "2025-12-19T20:36:14+00:00" }, { "name": "doctrine/persistence", - "version": "3.2.0", + "version": "4.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "63fee8c33bef740db6730eb2a750cd3da6495603" + "reference": "b9c49ad3558bb77ef973f4e173f2e9c2eca9be09" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/63fee8c33bef740db6730eb2a750cd3da6495603", - "reference": "63fee8c33bef740db6730eb2a750cd3da6495603", + "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": { - "composer/package-versions-deprecated": "^1.11", - "doctrine/coding-standard": "^11", - "doctrine/common": "^3.0", - "phpstan/phpstan": "1.9.4", - "phpstan/phpstan-phpunit": "^1", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "vimeo/psalm": "4.30.0 || 5.3.0" + "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": { @@ -1736,7 +3944,7 @@ ], "support": { "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/3.2.0" + "source": "https://github.com/doctrine/persistence/tree/4.1.1" }, "funding": [ { @@ -1752,27 +3960,30 @@ "type": "tidelift" } ], - "time": "2023-05-17T18:32:04+00:00" + "time": "2025-10-16T20:13:18+00:00" }, { "name": "doctrine/sql-formatter", - "version": "1.1.3", + "version": "1.5.3", "source": { "type": "git", "url": "https://github.com/doctrine/sql-formatter.git", - "reference": "25a06c7bf4c6b8218f47928654252863ffc890a5" + "reference": "a8af23a8e9d622505baa2997465782cbe8bb7fc7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/25a06c7bf4c6b8218f47928654252863ffc890a5", - "reference": "25a06c7bf4c6b8218f47928654252863ffc890a5", + "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/a8af23a8e9d622505baa2997465782cbe8bb7fc7", + "reference": "a8af23a8e9d622505baa2997465782cbe8bb7fc7", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4" + "doctrine/coding-standard": "^14", + "ergebnis/phpunit-slow-test-detector": "^2.20", + "phpstan/phpstan": "^2.1.31", + "phpunit/phpunit": "^10.5.58" }, "bin": [ "bin/sql-formatter" @@ -1802,30 +4013,30 @@ ], "support": { "issues": "https://github.com/doctrine/sql-formatter/issues", - "source": "https://github.com/doctrine/sql-formatter/tree/1.1.3" + "source": "https://github.com/doctrine/sql-formatter/tree/1.5.3" }, - "time": "2022-05-23T21:33:49+00:00" + "time": "2025-10-26T09:35:14+00:00" }, { "name": "dompdf/dompdf", - "version": "dev-master", + "version": "v3.1.4", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "87bea32efe0b0db309e1d31537201f64d5508280" + "reference": "db712c90c5b9868df3600e64e68da62e78a34623" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/87bea32efe0b0db309e1d31537201f64d5508280", - "reference": "87bea32efe0b0db309e1d31537201f64d5508280", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/db712c90c5b9868df3600e64e68da62e78a34623", + "reference": "db712c90c5b9868df3600e64e68da62e78a34623", "shasum": "" }, "require": { + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", "ext-dom": "*", "ext-mbstring": "*", "masterminds/html5": "^2.0", - "phenx/php-font-lib": ">=0.5.4 <1.0.0", - "phenx/php-svg-lib": ">=0.3.3 <1.0.0", "php": "^7.1 || ^8.0" }, "require-dev": { @@ -1833,9 +4044,9 @@ "ext-json": "*", "ext-zip": "*", "mockery/mockery": "^1.3", - "phpunit/phpunit": "^7.5 || ^8 || ^9", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11", "squizlabs/php_codesniffer": "^3.5", - "symfony/process": "^4.4 || ^5.4 || ^6.2" + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" }, "suggest": { "ext-gd": "Needed to process images", @@ -1843,7 +4054,6 @@ "ext-imagick": "Improves image processing performance", "ext-zlib": "Needed for pdf stream compression" }, - "default-branch": true, "type": "library", "autoload": { "psr-4": { @@ -1867,22 +4077,113 @@ "homepage": "https://github.com/dompdf/dompdf", "support": { "issues": "https://github.com/dompdf/dompdf/issues", - "source": "https://github.com/dompdf/dompdf/tree/master" + "source": "https://github.com/dompdf/dompdf/tree/v3.1.4" }, - "time": "2023-06-23T12:41:01+00:00" + "time": "2025-10-29T12:43:30+00:00" }, { - "name": "egulias/email-validator", - "version": "4.0.1", + "name": "dompdf/php-font-lib", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/egulias/EmailValidator.git", - "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff" + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/3a85486b709bc384dae8eb78fb2eec649bdb64ff", - "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.1" + }, + "time": "2024-12-02T14:37:59+00:00" + }, + { + "name": "dompdf/php-svg-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4 || ^9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2" + }, + "time": "2026-01-02T16:01:13+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", "shasum": "" }, "require": { @@ -1891,8 +4192,8 @@ "symfony/polyfill-intl-idn": "^1.26" }, "require-dev": { - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^4.30" + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -1928,7 +4229,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.1" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" }, "funding": [ { @@ -1936,102 +4237,46 @@ "type": "github" } ], - "time": "2023-01-14T14:17:03+00:00" + "time": "2025-03-06T22:45:56+00:00" }, { - "name": "erusev/parsedown", - "version": "1.7.4", + "name": "ergebnis/classy", + "version": "1.9.0", "source": { "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + "url": "https://github.com/ergebnis/classy.git", + "reference": "05c3ac7d8d9d337c4cf1d5602a339f57cb2a27ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "url": "https://api.github.com/repos/ergebnis/classy/zipball/05c3ac7d8d9d337c4cf1d5602a339f57cb2a27ef", + "reference": "05c3ac7d8d9d337c4cf1d5602a339f57cb2a27ef", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": ">=5.3.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": { - "phpunit/phpunit": "^4.8.35" + "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-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.0", - "source": { - "type": "git", - "url": "https://github.com/florianv/exchanger.git", - "reference": "be3e4b316a0fd90bac186cc8b8206e995df161ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/florianv/exchanger/zipball/be3e4b316a0fd90bac186cc8b8206e995df161ba", - "reference": "be3e4b316a0fd90bac186cc8b8206e995df161ba", - "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", - "php-http/message-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/" + "Ergebnis\\Classy\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2040,259 +4285,50 @@ ], "authors": [ { - "name": "Florian Voutzinos", - "email": "florian@voutzinos.com", - "homepage": "https://voutzinos.com" + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" } ], - "description": "Currency exchange rates framework for PHP", - "homepage": "https://github.com/florianv/exchanger", + "description": "Provides a finder for classy constructs (classes, enums, interfaces, and traits).", + "homepage": "https://github.com/ergebnis/classy", "keywords": [ - "Rate", - "conversion", - "currency", - "exchange rates", - "money" + "classes", + "classy", + "constructs", + "finder", + "interfaces", + "traits" ], "support": { - "issues": "https://github.com/florianv/exchanger/issues", - "source": "https://github.com/florianv/exchanger/tree/2.8.0" + "issues": "https://github.com/ergebnis/classy/issues", + "source": "https://github.com/ergebnis/classy" }, - "time": "2022-12-11T05:52:51+00:00" - }, - { - "name": "florianv/swap", - "version": "4.3.0", - "source": { - "type": "git", - "url": "https://github.com/florianv/swap.git", - "reference": "88edd27fcb95bdc58bbbf9e4b00539a2843d97fd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/florianv/swap/zipball/88edd27fcb95bdc58bbbf9e4b00539a2843d97fd", - "reference": "88edd27fcb95bdc58bbbf9e4b00539a2843d97fd", - "shasum": "" - }, - "require": { - "florianv/exchanger": "^2.0", - "php": "^7.1.3 || ^8.0" - }, - "require-dev": { - "nyholm/psr7": "^1.0", - "php-http/message": "^1.7", - "php-http/mock-client": "^1.0", - "phpunit/phpunit": "^7 || ^8 || ^9" - }, - "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" - } - ], - "description": "Exchange rates library for PHP", - "keywords": [ - "Rate", - "conversion", - "currency", - "exchange rates", - "money" - ], - "support": { - "issues": "https://github.com/florianv/swap/issues", - "source": "https://github.com/florianv/swap/tree/4.3.0" - }, - "time": "2020-12-28T10:14:12+00:00" - }, - { - "name": "florianv/swap-bundle", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/florianv/symfony-swap.git", - "reference": "fc6154976533e386ac6783c02ff19ab65aed4029" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/florianv/symfony-swap/zipball/fc6154976533e386ac6783c02ff19ab65aed4029", - "reference": "fc6154976533e386ac6783c02ff19ab65aed4029", - "shasum": "" - }, - "require": { - "florianv/swap": "^4.0", - "php": "^7.1.3|^8.0", - "symfony/framework-bundle": "~3.0|~4.0|~5.0|~6.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" - }, - "suggest": { - "symfony/cache": "For caching" - }, - "default-branch": true, - "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" - } - ], - "description": "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/florianv/symfony-swap/issues", - "source": "https://github.com/florianv/symfony-swap/tree/master" - }, - "time": "2023-01-12T08:17:02+00:00" - }, - { - "name": "friendsofphp/proxy-manager-lts", - "version": "v1.0.16", - "source": { - "type": "git", - "url": "https://github.com/FriendsOfPHP/proxy-manager-lts.git", - "reference": "ecadbdc9052e4ad08c60c8a02268712e50427f7c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/ecadbdc9052e4ad08c60c8a02268712e50427f7c", - "reference": "ecadbdc9052e4ad08c60c8a02268712e50427f7c", - "shasum": "" - }, - "require": { - "laminas/laminas-code": "~3.4.1|^4.0", - "php": ">=7.1", - "symfony/filesystem": "^4.4.17|^5.0|^6.0|^7.0" - }, - "conflict": { - "laminas/laminas-stdlib": "<3.2.1", - "zendframework/zend-stdlib": "<3.2.1" - }, - "replace": { - "ocramius/proxy-manager": "^2.1" - }, - "require-dev": { - "ext-phar": "*", - "symfony/phpunit-bridge": "^5.4|^6.0|^7.0" - }, - "type": "library", - "extra": { - "thanks": { - "name": "ocramius/proxy-manager", - "url": "https://github.com/Ocramius/ProxyManager" - } - }, - "autoload": { - "psr-4": { - "ProxyManager\\": "src/ProxyManager" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - } - ], - "description": "Adding support for a wider range of PHP versions to ocramius/proxy-manager", - "homepage": "https://github.com/FriendsOfPHP/proxy-manager-lts", - "keywords": [ - "aop", - "lazy loading", - "proxy", - "proxy pattern", - "service proxies" - ], - "support": { - "issues": "https://github.com/FriendsOfPHP/proxy-manager-lts/issues", - "source": "https://github.com/FriendsOfPHP/proxy-manager-lts/tree/v1.0.16" - }, - "funding": [ - { - "url": "https://github.com/Ocramius", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ocramius/proxy-manager", - "type": "tidelift" - } - ], - "time": "2023-05-24T07:17:17+00:00" + "time": "2025-09-04T10:17:22+00:00" }, { "name": "gregwar/captcha", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/Gregwar/Captcha.git", - "reference": "6e5b61b66ac89885b505153f4ef9a74ffa5b3074" + "reference": "4edbcd09fde4353b94ce550f43460eba73baf2cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/6e5b61b66ac89885b505153f4ef9a74ffa5b3074", - "reference": "6e5b61b66ac89885b505153f4ef9a74ffa5b3074", + "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": { @@ -2324,41 +4360,42 @@ ], "support": { "issues": "https://github.com/Gregwar/Captcha/issues", - "source": "https://github.com/Gregwar/Captcha/tree/v1.2.0" + "source": "https://github.com/Gregwar/Captcha/tree/v1.3.0" }, - "time": "2023-03-24T22:12:41+00:00" + "time": "2025-06-23T12:25:54+00:00" }, { "name": "gregwar/captcha-bundle", - "version": "v2.2.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/Gregwar/CaptchaBundle.git", - "reference": "2b55ba41fd890f1a94d30e53a530c344bf12d6a5" + "reference": "090a3754f02cadb7ecdb531b090322dbe5c03c75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Gregwar/CaptchaBundle/zipball/2b55ba41fd890f1a94d30e53a530c344bf12d6a5", - "reference": "2b55ba41fd890f1a94d30e53a530c344bf12d6a5", + "url": "https://api.github.com/repos/Gregwar/CaptchaBundle/zipball/090a3754f02cadb7ecdb531b090322dbe5c03c75", + "reference": "090a3754f02cadb7ecdb531b090322dbe5c03c75", "shasum": "" }, "require": { "ext-gd": "*", - "gregwar/captcha": "^1.1.9", - "php": ">=7.1.3", - "symfony/form": "~5.0|~6.0", - "symfony/framework-bundle": "~5.0|~6.0", - "symfony/translation": "~5.0|^6.0", - "twig/twig": "^2.10|^3.0" + "gregwar/captcha": "^1.2.1", + "php": ">=8.0.2", + "symfony/form": "~6.0|~7.0", + "symfony/framework-bundle": "~6.0|~7.0", + "symfony/translation": "~6.0|^7.0", + "twig/twig": "^3.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.66", - "symplify/easy-coding-standard": "^6.1" + "friendsofphp/php-cs-fixer": "^3.45", + "phpstan/phpstan": "^1.10", + "symplify/easy-coding-standard": "^12" }, "type": "symfony-bundle", "autoload": { "psr-4": { - "Gregwar\\CaptchaBundle\\": "/" + "Gregwar\\CaptchaBundle\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2369,7 +4406,7 @@ { "name": "Grégoire Passault", "email": "g.passault@gmail.com", - "homepage": "http://www.gregwar.com/" + "homepage": "https://www.gregwar.com/" }, { "name": "Jeremy Livingston", @@ -2390,28 +4427,28 @@ ], "support": { "issues": "https://github.com/Gregwar/CaptchaBundle/issues", - "source": "https://github.com/Gregwar/CaptchaBundle/tree/v2.2.0" + "source": "https://github.com/Gregwar/CaptchaBundle/tree/v2.4.0" }, - "time": "2022-01-11T08:28:06+00:00" + "time": "2025-06-24T10:25:23+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.7.0", + "version": "7.10.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5" + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5", - "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0", - "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "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" @@ -2420,11 +4457,11 @@ "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "guzzle/client-integration-tests": "3.0.2", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -2502,7 +4539,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.7.0" + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, "funding": [ { @@ -2518,28 +4555,28 @@ "type": "tidelift" } ], - "time": "2023-05-21T14:04:53+00:00" + "time": "2025-08-23T22:36:01+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.1", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "111166291a0f8130081195ac4556a5587d7f1b5d" + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", - "reference": "111166291a0f8130081195ac4556a5587d7f1b5d", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "type": "library", "extra": { @@ -2585,7 +4622,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.1" + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, "funding": [ { @@ -2601,20 +4638,20 @@ "type": "tidelift" } ], - "time": "2023-08-03T15:11:55+00:00" + "time": "2025-08-22T14:34:08+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.6.0", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/8bd7c33a0734ae1c5d074360512beb716bef3f77", - "reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { @@ -2628,9 +4665,9 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -2701,7 +4738,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.0" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, "funding": [ { @@ -2717,24 +4754,86 @@ "type": "tidelift" } ], - "time": "2023-08-03T15:06:02+00:00" + "time": "2025-08-23T21:21:41+00:00" }, { - "name": "imagine/imagine", - "version": "1.3.5", + "name": "hshn/base64-encoded-file", + "version": "v5.0.3", "source": { "type": "git", - "url": "https://github.com/php-imagine/Imagine.git", - "reference": "7151d553edec4dc2bbac60419f7a74ff34700e7f" + "url": "https://github.com/hshn/base64-encoded-file.git", + "reference": "74984c7e69fbed9378dbf1d64e632522cc1b6d95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-imagine/Imagine/zipball/7151d553edec4dc2bbac60419f7a74ff34700e7f", - "reference": "7151d553edec4dc2bbac60419f7a74ff34700e7f", + "url": "https://api.github.com/repos/hshn/base64-encoded-file/zipball/74984c7e69fbed9378dbf1d64e632522cc1b6d95", + "reference": "74984c7e69fbed9378dbf1d64e632522cc1b6d95", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^8.1.0", + "symfony/http-foundation": "^5.4 || ^6.0 || ^7.0", + "symfony/mime": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0.0", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/form": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/serializer": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "symfony/config": "to use the bundle in a Symfony project", + "symfony/dependency-injection": "to use the bundle in a Symfony project", + "symfony/form": "to use base64_encoded_file type", + "symfony/http-kernel": "to use the bundle in a Symfony project", + "symfony/serializer": "to convert a base64 string to a Base64EncodedFile object" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hshn\\Base64EncodedFile\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shota Hoshino", + "email": "sht.hshn@gmail.com" + } + ], + "description": "Provides handling base64 encoded files, and the integration of symfony/form", + "support": { + "issues": "https://github.com/hshn/base64-encoded-file/issues", + "source": "https://github.com/hshn/base64-encoded-file/tree/v5.0.3" + }, + "time": "2025-10-06T10:34:52+00:00" + }, + { + "name": "imagine/imagine", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/php-imagine/Imagine.git", + "reference": "8b130cd281efdea67e52d5f0f998572eb62d2f04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-imagine/Imagine/zipball/8b130cd281efdea67e52d5f0f998572eb62d2f04", + "reference": "8b130cd281efdea67e52d5f0f998572eb62d2f04", + "shasum": "" + }, + "require": { + "php": ">=7.1" }, "require-dev": { "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4 || ^9.3" @@ -2767,7 +4866,7 @@ "homepage": "http://avalanche123.com" } ], - "description": "Image processing for PHP 5.3", + "description": "Image processing for PHP", "homepage": "http://imagine.readthedocs.org/", "keywords": [ "drawing", @@ -2777,33 +4876,34 @@ ], "support": { "issues": "https://github.com/php-imagine/Imagine/issues", - "source": "https://github.com/php-imagine/Imagine/tree/1.3.5" + "source": "https://github.com/php-imagine/Imagine/tree/1.5.1" }, - "time": "2023-06-07T14:49:52+00:00" + "time": "2025-12-09T15:27:47+00:00" }, { "name": "jbtronics/2fa-webauthn", - "version": "v2.0.1", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/jbtronics/2fa-webauthn.git", - "reference": "0bd4222b21cec7c4b7693e43dc4093d965329e00" + "reference": "542424bcc51f06932cbecfd7b75afbb5e107c9ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jbtronics/2fa-webauthn/zipball/0bd4222b21cec7c4b7693e43dc4093d965329e00", - "reference": "0bd4222b21cec7c4b7693e43dc4093d965329e00", + "url": "https://api.github.com/repos/jbtronics/2fa-webauthn/zipball/542424bcc51f06932cbecfd7b75afbb5e107c9ce", + "reference": "542424bcc51f06932cbecfd7b75afbb5e107c9ce", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7": "^1.5", - "php": "^8.1", - "scheb/2fa-bundle": "^6.0.0", - "symfony/framework-bundle": "^6.0", - "symfony/psr-http-message-bridge": "^2.1", - "symfony/uid": "^6.0", - "web-auth/webauthn-lib": "^4.0" + "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": "^5.2" }, "require-dev": { "phpunit/phpunit": "^9.5", @@ -2825,7 +4925,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", @@ -2836,30 +4936,30 @@ ], "support": { "issues": "https://github.com/jbtronics/2fa-webauthn/issues", - "source": "https://github.com/jbtronics/2fa-webauthn/tree/v2.0.1" + "source": "https://github.com/jbtronics/2fa-webauthn/tree/v3.0.0" }, - "time": "2023-07-17T21:39:09+00:00" + "time": "2025-08-14T15:12:48+00:00" }, { "name": "jbtronics/dompdf-font-loader-bundle", - "version": "v1.0.0", + "version": "v1.1.6", "source": { "type": "git", "url": "https://github.com/jbtronics/dompdf-font-loader-bundle.git", - "reference": "8323aa3d4c3e70536b958a44f159208a6a0f7fa2" + "reference": "5fb434f35544d5757292cd5471768dda3862c932" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jbtronics/dompdf-font-loader-bundle/zipball/8323aa3d4c3e70536b958a44f159208a6a0f7fa2", - "reference": "8323aa3d4c3e70536b958a44f159208a6a0f7fa2", + "url": "https://api.github.com/repos/jbtronics/dompdf-font-loader-bundle/zipball/5fb434f35544d5757292cd5471768dda3862c932", + "reference": "5fb434f35544d5757292cd5471768dda3862c932", "shasum": "" }, "require": { - "dompdf/dompdf": "v1.0.0|v2.0.3", + "dompdf/dompdf": "^1.0.0|^2.0.0|^3.0.0", "ext-json": "*", "php": "^8.1", - "symfony/finder": "^6.0", - "symfony/framework-bundle": "^6.0" + "symfony/finder": "^6.0|^7.0|^8.0", + "symfony/framework-bundle": "^6.0|^7.0|^8.0" }, "require-dev": { "phpunit/phpunit": "^9.5", @@ -2891,9 +4991,95 @@ ], "support": { "issues": "https://github.com/jbtronics/dompdf-font-loader-bundle/issues", - "source": "https://github.com/jbtronics/dompdf-font-loader-bundle/tree/v1.0.0" + "source": "https://github.com/jbtronics/dompdf-font-loader-bundle/tree/v1.1.6" }, - "time": "2023-07-02T00:21:14+00:00" + "time": "2025-11-30T22:19:12+00:00" + }, + { + "name": "jbtronics/settings-bundle", + "version": "v3.1.2", + "source": { + "type": "git", + "url": "https://github.com/jbtronics/settings-bundle.git", + "reference": "f16bce21b54d202baabfe05cb7c64a14d43b9671" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jbtronics/settings-bundle/zipball/f16bce21b54d202baabfe05cb7c64a14d43b9671", + "reference": "f16bce21b54d202baabfe05cb7c64a14d43b9671", + "shasum": "" + }, + "require": { + "ergebnis/classy": "^1.6", + "ext-json": "*", + "php": "^8.1", + "symfony/deprecation-contracts": "^3.4", + "symfony/form": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/translation": "^7.0|^6.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/validator": "^6.4|^7.0|^8.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|^8.0", + "symfony/phpunit-bridge": "^6.4|^7.0|^8.0", + "symfony/security-csrf": "^7.0|^6.4|^8.0", + "symfony/twig-bridge": "^6.4|^7.0|^8.0" + }, + "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.2" + }, + "funding": [ + { + "url": "https://www.paypal.me/do9jhb", + "type": "custom" + }, + { + "url": "https://github.com/jbtronics", + "type": "github" + } + ], + "time": "2025-11-30T22:22:49+00:00" }, { "name": "jfcherng/php-color-output", @@ -2956,16 +5142,16 @@ }, { "name": "jfcherng/php-diff", - "version": "6.15.3", + "version": "6.16.2", "source": { "type": "git", "url": "https://github.com/jfcherng/php-diff.git", - "reference": "39be09756f8eda115299add3f34dc64b4bc32b66" + "reference": "7f46bcfc582e81769237d0b3f6b8a548efe8799d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jfcherng/php-diff/zipball/39be09756f8eda115299add3f34dc64b4bc32b66", - "reference": "39be09756f8eda115299add3f34dc64b4bc32b66", + "url": "https://api.github.com/repos/jfcherng/php-diff/zipball/7f46bcfc582e81769237d0b3f6b8a548efe8799d", + "reference": "7f46bcfc582e81769237d0b3f6b8a548efe8799d", "shasum": "" }, "require": { @@ -2975,7 +5161,7 @@ "php": ">=7.4" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.8", + "friendsofphp/php-cs-fixer": "^3.51", "liip/rmt": "^1.6", "phan/phan": "^5", "phpunit/phpunit": "^9", @@ -3010,7 +5196,7 @@ ], "support": { "issues": "https://github.com/jfcherng/php-diff/issues", - "source": "https://github.com/jfcherng/php-diff/tree/6.15.3" + "source": "https://github.com/jfcherng/php-diff/tree/6.16.2" }, "funding": [ { @@ -3018,7 +5204,7 @@ "type": "custom" } ], - "time": "2023-06-15T12:29:57+00:00" + "time": "2024-03-10T17:40:29+00:00" }, { "name": "jfcherng/php-mb-string", @@ -3133,33 +5319,91 @@ "time": "2023-05-21T07:57:08+00:00" }, { - "name": "knpuniversity/oauth2-client-bundle", - "version": "v2.15.0", + "name": "kelunik/certificate", + "version": "v1.1.3", "source": { "type": "git", - "url": "https://github.com/knpuniversity/oauth2-client-bundle.git", - "reference": "9df0736d02eb20b953ec8e9986743611747d9ed9" + "url": "https://github.com/kelunik/certificate.git", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/9df0736d02eb20b953ec8e9986743611747d9ed9", - "reference": "9df0736d02eb20b953ec8e9986743611747d9ed9", + "url": "https://api.github.com/repos/kelunik/certificate/zipball/7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">=7.0" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^6 | 7 | ^8 | ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Kelunik\\Certificate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Access certificate details and transform between different formats.", + "keywords": [ + "DER", + "certificate", + "certificates", + "openssl", + "pem", + "x509" + ], + "support": { + "issues": "https://github.com/kelunik/certificate/issues", + "source": "https://github.com/kelunik/certificate/tree/v1.1.3" + }, + "time": "2023-02-03T21:26:53+00:00" + }, + { + "name": "knpuniversity/oauth2-client-bundle", + "version": "v2.20.1", + "source": { + "type": "git", + "url": "https://github.com/knpuniversity/oauth2-client-bundle.git", + "reference": "d59e4dc61484e777b6f19df2efcf8b1bcc03828a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/d59e4dc61484e777b6f19df2efcf8b1bcc03828a", + "reference": "d59e4dc61484e777b6f19df2efcf8b1bcc03828a", "shasum": "" }, "require": { "league/oauth2-client": "^2.0", - "php": ">=7.4", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/framework-bundle": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0" + "php": ">=8.1", + "symfony/dependency-injection": "^6.4|^7.3|^8.0", + "symfony/framework-bundle": "^6.4|^7.3|^8.0", + "symfony/http-foundation": "^6.4|^7.3|^8.0", + "symfony/routing": "^6.4|^7.3|^8.0", + "symfony/security-core": "^6.4|^7.3|^8.0", + "symfony/security-http": "^6.4|^7.3|^8.0" }, "require-dev": { "league/oauth2-facebook": "^1.1|^2.0", - "phpstan/phpstan": "^0.12", - "symfony/phpunit-bridge": "^5.3.1|^6.0", - "symfony/security-guard": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0" + "symfony/phpunit-bridge": "^7.3", + "symfony/yaml": "^6.4|^7.3|^8.0" }, "suggest": { "symfony/security-guard": "For integration with Symfony's Guard Security layer" @@ -3188,103 +5432,40 @@ ], "support": { "issues": "https://github.com/knpuniversity/oauth2-client-bundle/issues", - "source": "https://github.com/knpuniversity/oauth2-client-bundle/tree/v2.15.0" + "source": "https://github.com/knpuniversity/oauth2-client-bundle/tree/v2.20.1" }, - "time": "2023-05-03T16:44:38+00:00" - }, - { - "name": "laminas/laminas-code", - "version": "4.11.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-code.git", - "reference": "169123b3ede20a9193480c53de2a8194f8c073ec" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-code/zipball/169123b3ede20a9193480c53de2a8194f8c073ec", - "reference": "169123b3ede20a9193480c53de2a8194f8c073ec", - "shasum": "" - }, - "require": { - "php": "~8.1.0 || ~8.2.0" - }, - "require-dev": { - "doctrine/annotations": "^2.0.0", - "ext-phar": "*", - "laminas/laminas-coding-standard": "^2.3.0", - "laminas/laminas-stdlib": "^3.6.1", - "phpunit/phpunit": "^10.0.9", - "psalm/plugin-phpunit": "^0.18.4", - "vimeo/psalm": "^5.7.1" - }, - "suggest": { - "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", - "laminas/laminas-stdlib": "Laminas\\Stdlib component" - }, - "type": "library", - "autoload": { - "psr-4": { - "Laminas\\Code\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", - "homepage": "https://laminas.dev", - "keywords": [ - "code", - "laminas", - "laminasframework" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-code/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-code/issues", - "rss": "https://github.com/laminas/laminas-code/releases.atom", - "source": "https://github.com/laminas/laminas-code" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2023-05-14T12:05:38+00:00" + "time": "2025-12-04T15:46:43+00:00" }, { "name": "lcobucci/clock", - "version": "3.0.0", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/lcobucci/clock.git", - "reference": "039ef98c6b57b101d10bd11d8fdfda12cbd996dc" + "reference": "db3713a61addfffd615b79bf0bc22f0ccc61b86b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/clock/zipball/039ef98c6b57b101d10bd11d8fdfda12cbd996dc", - "reference": "039ef98c6b57b101d10bd11d8fdfda12cbd996dc", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/db3713a61addfffd615b79bf0bc22f0ccc61b86b", + "reference": "db3713a61addfffd615b79bf0bc22f0ccc61b86b", "shasum": "" }, "require": { - "php": "~8.1.0 || ~8.2.0", + "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.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" + "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": { @@ -3305,7 +5486,7 @@ "description": "Yet another clock abstraction", "support": { "issues": "https://github.com/lcobucci/clock/issues", - "source": "https://github.com/lcobucci/clock/tree/3.0.0" + "source": "https://github.com/lcobucci/clock/tree/3.3.1" }, "funding": [ { @@ -3317,44 +5498,42 @@ "type": "patreon" } ], - "time": "2022-12-19T15:00:24+00:00" + "time": "2024-09-24T20:45:14+00:00" }, { "name": "lcobucci/jwt", - "version": "5.0.0", + "version": "5.6.0", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34" + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34", - "reference": "47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e", "shasum": "" }, "require": { - "ext-hash": "*", - "ext-json": "*", "ext-openssl": "*", "ext-sodium": "*", - "php": "~8.1.0 || ~8.2.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "psr/clock": "^1.0" }, "require-dev": { - "infection/infection": "^0.26.19", - "lcobucci/clock": "^3.0", - "lcobucci/coding-standard": "^9.0", - "phpbench/phpbench": "^1.2.8", + "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.3", - "phpstan/phpstan-deprecation-rules": "^1.1.2", - "phpstan/phpstan-phpunit": "^1.3.8", + "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.0.12" + "phpunit/phpunit": "^11.1" }, "suggest": { - "lcobucci/clock": ">= 3.0" + "lcobucci/clock": ">= 3.2" }, "type": "library", "autoload": { @@ -3380,7 +5559,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/5.0.0" + "source": "https://github.com/lcobucci/jwt/tree/5.6.0" }, "funding": [ { @@ -3392,39 +5571,235 @@ "type": "patreon" } ], - "time": "2023-02-25T21:35:16+00:00" + "time": "2025-10-17T11:30:53+00:00" }, { - "name": "league/csv", - "version": "9.8.0", + "name": "league/commonmark", + "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/thephpleague/csv.git", - "reference": "9d2e0265c5d90f5dd601bc65ff717e05cec19b47" + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/9d2e0265c5d90f5dd601bc65ff717e05cec19b47", - "reference": "9d2e0265c5d90f5dd601bc65ff717e05cec19b47", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb", "shasum": "" }, "require": { - "ext-json": "*", "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": { + "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.9-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "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": { + "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://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://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2025-11-26T21:48:24+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", "php": "^7.4 || ^8.0" }, "require-dev": { - "ext-curl": "*", + "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": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "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://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, + { + "name": "league/csv", + "version": "9.28.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/csv.git", + "reference": "6582ace29ae09ba5b07049d40ea13eb19c8b5073" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/6582ace29ae09ba5b07049d40ea13eb19c8b5073", + "reference": "6582ace29ae09ba5b07049d40ea13eb19c8b5073", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1.2" + }, + "require-dev": { "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.92.3", + "phpbench/phpbench": "^1.4.3", + "phpstan/phpstan": "^1.12.32", + "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.5.4", + "symfony/var-dumper": "^6.4.8 || ^7.4.0 || ^8.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": { @@ -3476,7 +5851,7 @@ "type": "github" } ], - "time": "2022-01-04T00:13:07+00:00" + "time": "2025-12-27T15:18:42+00:00" }, { "name": "league/html-to-markdown", @@ -3569,35 +5944,30 @@ }, { "name": "league/oauth2-client", - "version": "2.7.0", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth2-client.git", - "reference": "160d6274b03562ebeb55ed18399281d8118b76c8" + "reference": "26e8c5da4f3d78cede7021e09b1330a0fc093d5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/160d6274b03562ebeb55ed18399281d8118b76c8", - "reference": "160d6274b03562ebeb55ed18399281d8118b76c8", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/26e8c5da4f3d78cede7021e09b1330a0fc093d5e", + "reference": "26e8c5da4f3d78cede7021e09b1330a0fc093d5e", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "^6.0 || ^7.0", - "paragonie/random_compat": "^1 || ^2 || ^9.99", - "php": "^5.6 || ^7.0 || ^8.0" + "ext-json": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "php": "^7.1 || >=8.0.0 <8.6.0" }, "require-dev": { "mockery/mockery": "^1.3.5", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", - "squizlabs/php_codesniffer": "^2.3 || ^3.0" + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.11" }, "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.0.x-dev" - } - }, "autoload": { "psr-4": { "League\\OAuth2\\Client\\": "src/" @@ -3633,57 +6003,326 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth2-client/issues", - "source": "https://github.com/thephpleague/oauth2-client/tree/2.7.0" + "source": "https://github.com/thephpleague/oauth2-client/tree/2.9.0" }, - "time": "2023-04-16T18:19:15+00:00" + "time": "2025-11-25T22:17:17+00:00" }, { - "name": "liip/imagine-bundle", - "version": "2.11.0", + "name": "league/uri", + "version": "7.7.0", "source": { "type": "git", - "url": "https://github.com/liip/LiipImagineBundle.git", - "reference": "2e943e6be4309ec90fcff90227053898203c1c8e" + "url": "https://github.com/thephpleague/uri.git", + "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/2e943e6be4309ec90fcff90227053898203c1c8e", - "reference": "2e943e6be4309ec90fcff90227053898203c1c8e", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/8d587cddee53490f9b82bf203d3a9aa7ea4f9807", + "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.7", + "php": "^8.1", + "psr/http-factory": "^1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", + "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", + "league/uri-components": "Needed to easily manipulate URI objects components", + "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "URN", + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc2141", + "rfc3986", + "rfc3987", + "rfc6570", + "rfc8141", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.7.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2025-12-07T16:02:06+00:00" + }, + { + "name": "league/uri-components", + "version": "7.7.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-components.git", + "reference": "005f8693ce8c1f16f80e88a05cbf08da04c1c374" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-components/zipball/005f8693ce8c1f16f80e88a05cbf08da04c1c374", + "reference": "005f8693ce8c1f16f80e88a05cbf08da04c1c374", + "shasum": "" + }, + "require": { + "league/uri": "^7.7", + "php": "^8.1" + }, + "suggest": { + "bakame/aide-uri": "A polyfill for PHP8.1 until PHP8.4 to add support to PHP Native URI parser", + "ext-bcmath": "to improve IPV4 host parsing", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "ext-mbstring": "to use the sorting algorithm of URLSearchParams", + "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", + "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI components manipulation library", + "homepage": "http://uri.thephpleague.com", + "keywords": [ + "authority", + "components", + "fragment", + "host", + "middleware", + "modifier", + "path", + "port", + "query", + "rfc3986", + "scheme", + "uri", + "url", + "userinfo" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-components/tree/7.7.0" + }, + "funding": [ + { + "url": "https://github.com/nyamsprod", + "type": "github" + } + ], + "time": "2025-12-07T16:02:56+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.7.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/62ccc1a0435e1c54e10ee6022df28d6c04c2946c", + "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.7.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2025-12-07T16:03:21+00:00" + }, + { + "name": "liip/imagine-bundle", + "version": "2.16.0", + "source": { + "type": "git", + "url": "https://github.com/liip/LiipImagineBundle.git", + "reference": "335121ef65d9841af9b40a850aa143cd6b61f847" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/335121ef65d9841af9b40a850aa143cd6b61f847", + "reference": "335121ef65d9841af9b40a850aa143cd6b61f847", "shasum": "" }, "require": { "ext-mbstring": "*", "imagine/imagine": "^1.3.2", - "php": "^7.1|^8.0", - "symfony/filesystem": "^3.4|^4.4|^5.3|^6.0", - "symfony/finder": "^3.4|^4.4|^5.3|^6.0", - "symfony/framework-bundle": "^3.4.23|^4.4|^5.3|^6.0", - "symfony/mime": "^4.4|^5.3|^6.0", - "symfony/options-resolver": "^3.4|^4.4|^5.3|^6.0", - "symfony/process": "^3.4|^4.4|^5.3|^6.0", + "php": "^7.2|^8.0", + "symfony/dependency-injection": "^5.4|^6.4|^7.4|^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3", + "symfony/filesystem": "^5.4|^6.4|^7.3|^8.0", + "symfony/finder": "^5.4|^6.4|^7.3|^8.0", + "symfony/framework-bundle": "^5.4|^6.4|^7.3|^8.0", + "symfony/mime": "^5.4|^6.4|^7.3|^8.0", + "symfony/options-resolver": "^5.4|^6.4|^7.3|^8.0", + "symfony/process": "^5.4|^6.4|^7.3|^8.0", "twig/twig": "^1.44|^2.9|^3.0" }, "require-dev": { "amazonwebservices/aws-sdk-for-php": "^1.0", - "aws/aws-sdk-php": "^2.4", + "aws/aws-sdk-php": "^2.4|^3.0", "doctrine/cache": "^1.11|^2.0", "doctrine/persistence": "^1.3|^2.0", "enqueue/enqueue-bundle": "^0.9|^0.10", "ext-gd": "*", "league/flysystem": "^1.0|^2.0|^3.0", - "phpstan/phpstan": "^0.12.64", + "phpstan/phpstan": "^1.10.0", "psr/cache": "^1.0|^2.0|^3.0", "psr/log": "^1.0", - "symfony/browser-kit": "^3.4|^4.4|^5.3|^6.0", - "symfony/cache": "^3.4|^4.4|^5.3|^6.0", - "symfony/console": "^3.4|^4.4|^5.3|^6.0", - "symfony/dependency-injection": "^3.4|^4.4|^5.3|^6.0", - "symfony/form": "^3.4|^4.4|^5.3|^6.0", - "symfony/messenger": "^4.4|^5.3|^6.0", - "symfony/phpunit-bridge": "^5.3", - "symfony/templating": "^3.4|^4.4|^5.3|^6.0", - "symfony/validator": "^3.4|^4.4|^5.3|^6.0", - "symfony/yaml": "^3.4|^4.4|^5.3|^6.0" + "symfony/asset": "^5.4|^6.4|^7.3|^8.0", + "symfony/browser-kit": "^5.4|^6.4|^7.3|^8.0", + "symfony/cache": "^5.4|^6.4|^7.3|^8.0", + "symfony/console": "^5.4|^6.4|^7.3|^8.0", + "symfony/form": "^5.4|^6.4|^7.3|^8.0", + "symfony/messenger": "^5.4|^6.4|^7.3|^8.0", + "symfony/phpunit-bridge": "^7.3", + "symfony/templating": "^5.4|^6.4|^7.3|^8.0", + "symfony/validator": "^5.4|^6.4|^7.3|^8.0", + "symfony/yaml": "^5.4|^6.4|^7.3|^8.0" }, "suggest": { "alcaeus/mongo-php-adapter": "required for mongodb components", @@ -3695,10 +6334,12 @@ "ext-gd": "required to use gd driver", "ext-gmagick": "required to use gmagick driver", "ext-imagick": "required to use imagick driver", + "ext-json": "required to read JSON manifest versioning", "ext-mongodb": "required for mongodb components", "league/flysystem": "required to use FlySystem data loader or cache resolver", "monolog/monolog": "A psr/log compatible logger is required to enable logging", "rokka/imagine-vips": "required to use 'vips' driver", + "symfony/asset": "If you want to use asset versioning", "symfony/messenger": "If you like to process images in background", "symfony/templating": "required to use deprecated Templating component instead of Twig" }, @@ -3722,7 +6363,7 @@ } ], "description": "This bundle provides an image manipulation abstraction toolkit for Symfony-based projects.", - "homepage": "http://liip.ch", + "homepage": "https://www.liip.ch", "keywords": [ "bundle", "image", @@ -3736,9 +6377,9 @@ ], "support": { "issues": "https://github.com/liip/LiipImagineBundle/issues", - "source": "https://github.com/liip/LiipImagineBundle/tree/2.11.0" + "source": "https://github.com/liip/LiipImagineBundle/tree/2.16.0" }, - "time": "2023-05-16T06:13:14+00:00" + "time": "2025-12-01T10:49:05+00:00" }, { "name": "lorenzo/pinky", @@ -3794,17 +6435,199 @@ "time": "2023-07-31T13:36:50+00:00" }, { - "name": "masterminds/html5", - "version": "2.8.1", + "name": "maennchen/zipstream-php", + "version": "2.1.0", "source": { "type": "git", - "url": "https://github.com/Masterminds/html5-php.git", - "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf" + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f47dcf3c70c584de14f21143c55d9939631bc6cf", - "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/c4c5803cc1f93df3d2448478ef79394a5981cc58", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58", + "shasum": "" + }, + "require": { + "myclabs/php-enum": "^1.5", + "php": ">= 7.1", + "psr/http-message": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "ext-zip": "*", + "guzzlehttp/guzzle": ">= 6.3", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": ">= 7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.1.0" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + }, + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "time": "2020-05-30T13:11:16+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", "shasum": "" }, "require": { @@ -3812,7 +6635,7 @@ "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8" + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" }, "type": "library", "extra": { @@ -3856,22 +6679,22 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.8.1" + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" }, - "time": "2023-05-10T11:58:31+00:00" + "time": "2025-07-25T09:04:22+00:00" }, { "name": "monolog/monolog", - "version": "3.4.0", + "version": "3.10.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "e2392369686d420ca32df3803de28b5d6f76867d" + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/e2392369686d420ca32df3803de28b5d6f76867d", - "reference": "e2392369686d420ca32df3803de28b5d6f76867d", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", "shasum": "" }, "require": { @@ -3889,14 +6712,16 @@ "graylog2/gelf-php": "^1.4.2 || ^2.0", "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/psr7": "^2.2", - "mongodb/mongodb": "^1.8", + "mongodb/mongodb": "^1.8 || ^2.0", "php-amqplib/php-amqplib": "~2.4 || ^3", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "^10.1", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", "predis/predis": "^1.1 || ^2", - "ruflin/elastica": "^7", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", "symfony/mailer": "^5.4 || ^6", "symfony/mime": "^5.4 || ^6" }, @@ -3947,7 +6772,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.4.0" + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" }, "funding": [ { @@ -3959,45 +6784,104 @@ "type": "tidelift" } ], - "time": "2023-06-21T08:46:11+00:00" + "time": "2026-01-02T08:56:05+00:00" }, { - "name": "nbgrp/onelogin-saml-bundle", - "version": "v1.3.2", + "name": "myclabs/php-enum", + "version": "1.8.5", "source": { "type": "git", - "url": "https://github.com/nbgrp/onelogin-saml-bundle.git", - "reference": "907a59431edcfbb962b2bb952d987693b63ca757" + "url": "https://github.com/myclabs/php-enum.git", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nbgrp/onelogin-saml-bundle/zipball/907a59431edcfbb962b2bb952d987693b63ca757", - "reference": "907a59431edcfbb962b2bb952d987693b63ca757", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/e7be26966b7398204a234f8673fdad5ac6277802", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2 || ^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "https://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2025-01-14T11:49:03+00:00" + }, + { + "name": "nbgrp/onelogin-saml-bundle", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/nbgrp/onelogin-saml-bundle.git", + "reference": "087402c69ef87e0a34d9b708661deecd00fd190a" + }, + "dist": { + "type": "zip", + "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", - "symfony/dependency-injection": "^6", + "symfony/config": "^7", + "symfony/dependency-injection": "^7", "symfony/deprecation-contracts": "^3", "symfony/event-dispatcher-contracts": "^3", - "symfony/http-foundation": "^6", - "symfony/http-kernel": "^6", - "symfony/routing": "^6", - "symfony/security-bundle": "^6", - "symfony/security-core": "^6", - "symfony/security-http": "^6" - }, - "conflict": { - "symfony/http-kernel": "<6.2", - "symfony/security-core": "<6.2" + "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", - "symfony/phpunit-bridge": "^6" + "phpunit/phpunit": "^11", + "symfony/event-dispatcher": "^7" }, "type": "symfony-bundle", "autoload": { @@ -4024,9 +6908,9 @@ ], "support": { "issues": "https://github.com/nbgrp/onelogin-saml-bundle/issues", - "source": "https://github.com/nbgrp/onelogin-saml-bundle/tree/v1.3.2" + "source": "https://github.com/nbgrp/onelogin-saml-bundle/tree/v2.1.0" }, - "time": "2023-03-22T20:23:42+00:00" + "time": "2025-09-26T08:45:17+00:00" }, { "name": "nelexa/zip", @@ -4102,41 +6986,107 @@ "time": "2022-06-17T11:17:46+00:00" }, { - "name": "nelmio/security-bundle", - "version": "v3.0.0", + "name": "nelmio/cors-bundle", + "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/nelmio/NelmioSecurityBundle.git", - "reference": "34699d40d81b58b6bd256e34489c799620dff2a4" + "url": "https://github.com/nelmio/NelmioCorsBundle.git", + "reference": "530217472204881cacd3671909f634b960c7b948" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nelmio/NelmioSecurityBundle/zipball/34699d40d81b58b6bd256e34489c799620dff2a4", - "reference": "34699d40d81b58b6bd256e34489c799620dff2a4", + "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/530217472204881cacd3671909f634b960c7b948", + "reference": "530217472204881cacd3671909f634b960c7b948", + "shasum": "" + }, + "require": { + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11.5", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.4", + "phpstan/phpstan-symfony": "^1.4.4", + "phpunit/phpunit": "^8" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Nelmio\\CorsBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nelmio", + "homepage": "http://nelm.io" + }, + { + "name": "Symfony Community", + "homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors" + } + ], + "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application", + "keywords": [ + "api", + "cors", + "crossdomain" + ], + "support": { + "issues": "https://github.com/nelmio/NelmioCorsBundle/issues", + "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.6.0" + }, + "time": "2025-10-23T06:57:22+00:00" + }, + { + "name": "nelmio/security-bundle", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/nelmio/NelmioSecurityBundle.git", + "reference": "9389ec28cd219d621d3d91c840a3df6f04c9f651" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nelmio/NelmioSecurityBundle/zipball/9389ec28cd219d621d3d91c840a3df6f04c9f651", + "reference": "9389ec28cd219d621d3d91c840a3df6f04c9f651", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "symfony/framework-bundle": "^4.4 || ^5.4 || ^6.0", - "symfony/http-kernel": "^4.4 || ^5.4 || ^6.0", - "symfony/security-core": "^4.4 || ^5.4 || ^6.0", - "symfony/security-csrf": "^4.4 || ^5.4 || ^6.0", - "symfony/security-http": "^4.4 || ^5.4 || ^6.0", - "symfony/yaml": "^4.4 || ^5.4 || ^6.0", + "symfony/deprecation-contracts": "^2.5 || ^3", + "symfony/framework-bundle": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "symfony/http-kernel": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "symfony/security-core": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "symfony/security-csrf": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "symfony/security-http": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "symfony/yaml": "^5.4 || ^6.3 || ^7.0 || ^8.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", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-symfony": "^2.0", + "phpunit/phpunit": "^9.5 || ^10.1 || ^11.0", "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/browser-kit": "^4.4 || ^5.4 || ^6.0", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "symfony/phpunit-bridge": "^6.0", - "symfony/twig-bundle": "^4.4 || ^5.4 || ^6.0", + "symfony/browser-kit": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "symfony/cache": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "symfony/phpunit-bridge": "^6.3 || ^7.0 || ^8.0", + "symfony/twig-bundle": "^5.4 || ^6.3 || ^7.0 || ^8.0", "twig/twig": "^2.10 || ^3.0" }, "type": "symfony-bundle", @@ -4170,95 +7120,193 @@ ], "support": { "issues": "https://github.com/nelmio/NelmioSecurityBundle/issues", - "source": "https://github.com/nelmio/NelmioSecurityBundle/tree/v3.0.0" + "source": "https://github.com/nelmio/NelmioSecurityBundle/tree/v3.7.0" }, - "time": "2022-03-17T07:30:15+00:00" + "time": "2025-12-30T14:05:13+00:00" }, { - "name": "nikic/php-parser", - "version": "v4.17.1", + "name": "nette/schema", + "version": "v1.3.3", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "url": "https://github.com/nette/schema.git", + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004", + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": ">=7.0" + "nette/utils": "^4.0", + "php": "8.1 - 8.5" }, "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "nette/tester": "^2.5.2", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.8" }, - "bin": [ - "bin/php-parse" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "1.3-dev" } }, "autoload": { "psr-4": { - "PhpParser\\": "lib/PhpParser" - } + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" ], "authors": [ { - "name": "Nikita Popov" + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" } ], - "description": "A PHP parser written in PHP", + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", "keywords": [ - "parser", - "php" + "config", + "nette" ], "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.3" }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2025-10-30T22:57:59+00:00" }, { - "name": "nikolaposa/version", - "version": "4.1.1", + "name": "nette/utils", + "version": "v4.1.1", "source": { "type": "git", - "url": "https://github.com/nikolaposa/version.git", - "reference": "f6bdd64be914940529b843a67335d6386d980cec" + "url": "https://github.com/nette/utils.git", + "reference": "c99059c0315591f1a0db7ad6002000288ab8dc72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikolaposa/version/zipball/f6bdd64be914940529b843a67335d6386d980cec", - "reference": "f6bdd64be914940529b843a67335d6386d980cec", + "url": "https://api.github.com/repos/nette/utils/zipball/c99059c0315591f1a0db7ad6002000288ab8dc72", + "reference": "c99059c0315591f1a0db7ad6002000288ab8dc72", "shasum": "" }, "require": { - "beberlei/assert": "^3.2", - "php": "^7.2 || ^8.0" + "php": "8.2 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.17", - "phpstan/phpstan": "^0.12.10", - "phpstan/phpstan-beberlei-assert": "^0.12.2", - "phpstan/phpstan-phpunit": "^0.12.6", - "phpunit/phpunit": "^8.0" + "jetbrains/phpstorm-attributes": "^1.2", + "nette/tester": "^2.5", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.9" + }, + "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": "4.1.x-dev" + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "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": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.1.1" + }, + "time": "2025-12-22T12:14:32+00:00" + }, + { + "name": "nikolaposa/version", + "version": "4.2.1", + "source": { + "type": "git", + "url": "https://github.com/nikolaposa/version.git", + "reference": "2b9ee2f0b09333b6ce00bd6b63132cdf1d7a1428" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikolaposa/version/zipball/2b9ee2f0b09333b6ce00bd6b63132cdf1d7a1428", + "reference": "2b9ee2f0b09333b6ce00bd6b63132cdf1d7a1428", + "shasum": "" + }, + "require": { + "beberlei/assert": "^3.2", + "php": "^8.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.44", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-beberlei-assert": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2.x-dev" } }, "autoload": { @@ -4287,79 +7335,22 @@ ], "support": { "issues": "https://github.com/nikolaposa/version/issues", - "source": "https://github.com/nikolaposa/version/tree/4.1.1" + "source": "https://github.com/nikolaposa/version/tree/4.2.1" }, - "time": "2023-08-04T17:13:40+00:00" - }, - { - "name": "nyholm/nsa", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/Nyholm/NSA.git", - "reference": "c264c17ed2aa8251c64ad289442ed53f64cdb283" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Nyholm/NSA/zipball/c264c17ed2aa8251c64ad289442ed53f64cdb283", - "reference": "c264c17ed2aa8251c64ad289442ed53f64cdb283", - "shasum": "" - }, - "require": { - "php": ">=7.1", - "webmozart/assert": "^1.1.0" - }, - "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Nyholm\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com" - } - ], - "description": "See everything and do whatever you want. No privacy rule will stop us. Used in tests, debugging and fixtures to access properties and methods.", - "homepage": "https://tnyholm.se", - "keywords": [ - "Fixture", - "debug", - "reflection", - "test" - ], - "support": { - "issues": "https://github.com/Nyholm/NSA/issues", - "source": "https://github.com/Nyholm/NSA/tree/1.3.0" - }, - "funding": [ - { - "url": "https://github.com/nyholm", - "type": "github" - } - ], - "time": "2021-07-15T18:25:37+00:00" + "time": "2025-03-24T19:12:02+00:00" }, { "name": "nyholm/psr7", - "version": "1.8.0", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/Nyholm/psr7.git", - "reference": "3cb4d163b58589e47b35103e8e5e6a6a475b47be" + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nyholm/psr7/zipball/3cb4d163b58589e47b35103e8e5e6a6a475b47be", - "reference": "3cb4d163b58589e47b35103e8e5e6a6a475b47be", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", "shasum": "" }, "require": { @@ -4412,7 +7403,7 @@ ], "support": { "issues": "https://github.com/Nyholm/psr7/issues", - "source": "https://github.com/Nyholm/psr7/tree/1.8.0" + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" }, "funding": [ { @@ -4424,63 +7415,73 @@ "type": "github" } ], - "time": "2023-05-02T11:26:24+00:00" + "time": "2024-09-09T07:06:30+00:00" }, { "name": "omines/datatables-bundle", - "version": "0.7.2", + "version": "0.10.7", "source": { "type": "git", "url": "https://github.com/omines/datatables-bundle.git", - "reference": "8e0dce49a271e0cfdf128d42bf81a336f8f02232" + "reference": "4cd6d27b12c79a1ed72b4953a86aedf289e8701d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/omines/datatables-bundle/zipball/8e0dce49a271e0cfdf128d42bf81a336f8f02232", - "reference": "8e0dce49a271e0cfdf128d42bf81a336f8f02232", + "url": "https://api.github.com/repos/omines/datatables-bundle/zipball/4cd6d27b12c79a1ed72b4953a86aedf289e8701d", + "reference": "4cd6d27b12c79a1ed72b4953a86aedf289e8701d", "shasum": "" }, "require": { - "php": ">=8.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/framework-bundle": "^5.4|^6.0", - "symfony/options-resolver": "^5.4|^6.0", - "symfony/property-access": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0" + "php": ">=8.2", + "symfony/event-dispatcher": "^6.4|^7.3|^8.0", + "symfony/framework-bundle": "^6.4|^7.3|^8.0", + "symfony/options-resolver": "^6.4|^7.3|^8.0", + "symfony/polyfill-mbstring": "^1.31.0", + "symfony/property-access": "^6.4|^7.3|^8.0", + "symfony/translation": "^6.4|^7.3|^8.0" + }, + "conflict": { + "doctrine/orm": "^3.0 <3.3" }, "require-dev": { - "doctrine/common": "^2.6|^3.3", - "doctrine/doctrine-bundle": "^2.7|^3.0", - "doctrine/orm": "^2.13.1", - "doctrine/persistence": "^2.0|^3.0.3", + "doctrine/common": "^3.5.0", + "doctrine/doctrine-bundle": "^2.18.1|^3.0.0@dev", + "doctrine/orm": "^2.19.3|^3.5.7@dev", + "doctrine/persistence": "^3.4.0|^4.1.1", "ext-curl": "*", "ext-json": "*", + "ext-mbstring": "*", + "ext-mongodb": "*", "ext-pdo_sqlite": "*", "ext-zip": "*", - "friendsofphp/php-cs-fixer": "^3.9.5", - "mongodb/mongodb": "^1.12", - "ocramius/package-versions": "^2.5", - "phpoffice/phpspreadsheet": "^1.24.1", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8.2", - "phpstan/phpstan-doctrine": "^1.3.12", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-symfony": "^1.2.9", - "ruflin/elastica": "^6.0|^7.2", - "symfony/browser-kit": "^5.4|^6.1.3", - "symfony/css-selector": "^5.4|^6.1.3", - "symfony/dom-crawler": "^5.4|^6.1.3", - "symfony/intl": "^5.4|^6.1", - "symfony/mime": "^5.4|^6.1.3", - "symfony/phpunit-bridge": "^5.4|^6.1.3", - "symfony/twig-bundle": "^5.4|^6.1.1", - "symfony/var-dumper": "^5.4|^6.1.3", - "symfony/yaml": "^5.4|^6.1.3" + "friendsofphp/php-cs-fixer": "^3.90.0", + "mongodb/mongodb": "^1.20.0|^2.1.2", + "openspout/openspout": "^4.28.5", + "phpoffice/phpspreadsheet": "^2.3.3|^3.9.2|^4.5.0|^5.2.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.32", + "phpstan/phpstan-doctrine": "^2.0.11", + "phpstan/phpstan-phpunit": "^2.0.8", + "phpstan/phpstan-symfony": "^2.0.8", + "phpunit/phpunit": "^11.5.44|^12.4.4", + "ruflin/elastica": "^7.3.2", + "symfony/browser-kit": "^6.4.13|^7.3|^8.0", + "symfony/css-selector": "^6.4.13|^7.3|^8.0", + "symfony/doctrine-bridge": "^6.4.13|^7.3|^8.0", + "symfony/dom-crawler": "^6.4.13|^7.3|^8.0", + "symfony/intl": "^6.4.13|^7.3|^8.0", + "symfony/mime": "^6.4.13|^7.3|^8.0", + "symfony/phpunit-bridge": "^7.3|^8.0", + "symfony/twig-bundle": "^6.4|^7.3|^8.0", + "symfony/var-dumper": "^6.4.13|^7.3|^8.0", + "symfony/var-exporter": "^v6.4.26|^7.3", + "symfony/yaml": "^6.4.13|^7.3|^8.0" }, "suggest": { "doctrine/doctrine-bundle": "For integrated access to Doctrine object managers", "doctrine/orm": "For full automated integration with Doctrine entities", "mongodb/mongodb": "For integration with MongoDB collections", + "openspout/openspout": "To use the OpenSpout Excel exporter", "phpoffice/phpspreadsheet": "To export the data from DataTables to Excel", "ruflin/elastica": "For integration with Elasticsearch indexes", "symfony/twig-bundle": "To use default Twig based rendering and TwigColumn" @@ -4488,7 +7489,7 @@ "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "0.7-dev" + "dev-master": "0.10-dev" } }, "autoload": { @@ -4504,12 +7505,12 @@ { "name": "Robbert Beesems", "email": "robbert.beesems@omines.com", - "homepage": "https://omines.nl/" + "homepage": "https://www.omines.nl/" }, { "name": "Niels Keurentjes", "email": "niels.keurentjes@omines.com", - "homepage": "https://omines.nl/" + "homepage": "https://www.omines.nl/" } ], "description": "Symfony DataTables Bundle with native Doctrine ORM, Elastica and MongoDB support", @@ -4526,27 +7527,33 @@ ], "support": { "issues": "https://github.com/omines/datatables-bundle/issues", - "source": "https://github.com/omines/datatables-bundle/tree/0.7.2" + "source": "https://github.com/omines/datatables-bundle/tree/0.10.7" }, - "time": "2023-04-24T09:09:02+00:00" + "funding": [ + { + "url": "https://github.com/curry684", + "type": "github" + } + ], + "time": "2025-11-28T21:20:14+00:00" }, { "name": "onelogin/php-saml", - "version": "4.1.0", + "version": "4.3.1", "source": { "type": "git", - "url": "https://github.com/onelogin/php-saml.git", - "reference": "b22a57ebd13e838b90df5d3346090bc37056409d" + "url": "https://github.com/SAML-Toolkits/php-saml.git", + "reference": "b009f160e4ac11f49366a45e0d45706b48429353" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/onelogin/php-saml/zipball/b22a57ebd13e838b90df5d3346090bc37056409d", - "reference": "b22a57ebd13e838b90df5d3346090bc37056409d", + "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/b009f160e4ac11f49366a45e0d45706b48429353", + "reference": "b009f160e4ac11f49366a45e0d45706b48429353", "shasum": "" }, "require": { "php": ">=7.3", - "robrichards/xmlseclibs": ">=3.1.1" + "robrichards/xmlseclibs": ">=3.1.4" }, "require-dev": { "pdepend/pdepend": "^2.8.0", @@ -4572,40 +7579,50 @@ "license": [ "MIT" ], - "description": "OneLogin PHP SAML Toolkit", - "homepage": "https://developers.onelogin.com/saml/php", + "description": "PHP SAML Toolkit", + "homepage": "https://github.com/SAML-Toolkits/php-saml", "keywords": [ + "Federation", "SAML2", - "onelogin", + "SSO", + "identity", "saml" ], "support": { - "email": "sixto.garcia@onelogin.com", - "issues": "https://github.com/onelogin/php-saml/issues", - "source": "https://github.com/onelogin/php-saml/" + "email": "sixto.martin.garcia@gmail.com", + "issues": "https://github.com/onelogin/SAML-Toolkits/issues", + "source": "https://github.com/onelogin/SAML-Toolkits/" }, - "time": "2022-07-15T20:44:36+00:00" + "funding": [ + { + "url": "https://github.com/SAML-Toolkits", + "type": "github" + } + ], + "time": "2025-12-09T10:50:49+00:00" }, { "name": "paragonie/constant_time_encoding", - "version": "v2.6.3", + "version": "v3.1.3", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "58c3f47f650c94ec05a151692652a868995d2938" + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", - "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "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": { @@ -4651,7 +7668,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2022-06-14T06:56:20+00:00" + "time": "2025-09-24T15:06:41+00:00" }, { "name": "paragonie/random_compat", @@ -4704,17 +7721,180 @@ "time": "2020-10-15T08:29:30+00:00" }, { - "name": "part-db/label-fonts", - "version": "v1.0.0", + "name": "paragonie/sodium_compat", + "version": "v1.24.0", "source": { "type": "git", - "url": "https://github.com/Part-DB/label-fonts.git", - "reference": "65f4a47d877f45e39804cd86a4fc65789b49ee2f" + "url": "https://github.com/paragonie/sodium_compat.git", + "reference": "2cb48f26130919f92f30650bdcc30e6f4ebe45ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Part-DB/label-fonts/zipball/65f4a47d877f45e39804cd86a4fc65789b49ee2f", - "reference": "65f4a47d877f45e39804cd86a4fc65789b49ee2f", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/2cb48f26130919f92f30650bdcc30e6f4ebe45ac", + "reference": "2cb48f26130919f92f30650bdcc30e6f4ebe45ac", + "shasum": "" + }, + "require": { + "paragonie/random_compat": ">=1", + "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^3|^4|^5|^6|^7|^8|^9" + }, + "suggest": { + "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", + "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com" + }, + { + "name": "Frank Denis", + "email": "jedisct1@pureftpd.org" + } + ], + "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", + "keywords": [ + "Authentication", + "BLAKE2b", + "ChaCha20", + "ChaCha20-Poly1305", + "Chapoly", + "Curve25519", + "Ed25519", + "EdDSA", + "Edwards-curve Digital Signature Algorithm", + "Elliptic Curve Diffie-Hellman", + "Poly1305", + "Pure-PHP cryptography", + "RFC 7748", + "RFC 8032", + "Salpoly", + "Salsa20", + "X25519", + "XChaCha20-Poly1305", + "XSalsa20-Poly1305", + "Xchacha20", + "Xsalsa20", + "aead", + "cryptography", + "ecdh", + "elliptic curve", + "elliptic curve cryptography", + "encryption", + "libsodium", + "php", + "public-key cryptography", + "secret-key cryptography", + "side-channel resistant" + ], + "support": { + "issues": "https://github.com/paragonie/sodium_compat/issues", + "source": "https://github.com/paragonie/sodium_compat/tree/v1.24.0" + }, + "time": "2025-12-30T16:16:35+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": "2025-09-05T19:48:23+00:00" + }, + { + "name": "part-db/label-fonts", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/Part-DB/label-fonts.git", + "reference": "c85aeb051d6492961a2c59bc291979f15ce60e88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Part-DB/label-fonts/zipball/c85aeb051d6492961a2c59bc291979f15ce60e88", + "reference": "c85aeb051d6492961a2c59bc291979f15ce60e88", "shasum": "" }, "type": "library", @@ -4737,112 +7917,165 @@ ], "support": { "issues": "https://github.com/Part-DB/label-fonts/issues", - "source": "https://github.com/Part-DB/label-fonts/tree/v1.0.0" + "source": "https://github.com/Part-DB/label-fonts/tree/v1.2.0" }, - "time": "2023-07-02T01:01:20+00:00" + "time": "2025-09-07T15:42:51+00:00" }, { - "name": "phenx/php-font-lib", - "version": "0.5.4", + "name": "part-db/swap", + "version": "v5.0.0", "source": { "type": "git", - "url": "https://github.com/dompdf/php-font-lib.git", - "reference": "dd448ad1ce34c63d09baccd05415e361300c35b4" + "url": "https://github.com/Part-DB/swap.git", + "reference": "4fa57dec2eb1cbe0f6b8c92a2c250ecbe80688fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/dd448ad1ce34c63d09baccd05415e361300c35b4", - "reference": "dd448ad1ce34c63d09baccd05415e361300c35b4", + "url": "https://api.github.com/repos/Part-DB/swap/zipball/4fa57dec2eb1cbe0f6b8c92a2c250ecbe80688fe", + "reference": "4fa57dec2eb1cbe0f6b8c92a2c250ecbe80688fe", "shasum": "" }, "require": { - "ext-mbstring": "*" + "part-db/exchanger": "^3.0", + "php": "^7.1.3 || ^8.0", + "php-http/message-factory": "^1.1" }, "require-dev": { - "symfony/phpunit-bridge": "^3 || ^4 || ^5" + "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": { - "FontLib\\": "src/FontLib" + "Swap\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0" + "MIT" ], "authors": [ { - "name": "Fabien Ménager", - "email": "fabien.menager@gmail.com" + "name": "Florian Voutzinos", + "email": "florian@voutzinos.com", + "homepage": "https://voutzinos.com" + }, + { + "name": "Jan Böhmer", + "email": "mail@jan-boehmer.de" } ], - "description": "A library to read, parse, export and make subsets of different types of font files.", - "homepage": "https://github.com/PhenX/php-font-lib", + "description": "Fork of florianv/swap modernized for use in Part-DB. Exchange rates library for PHP", + "keywords": [ + "Rate", + "conversion", + "currency", + "exchange rates", + "money" + ], "support": { - "issues": "https://github.com/dompdf/php-font-lib/issues", - "source": "https://github.com/dompdf/php-font-lib/tree/0.5.4" + "source": "https://github.com/Part-DB/swap/tree/v5.0.0" }, - "time": "2021-12-17T19:44:54+00:00" + "time": "2025-09-05T17:10:01+00:00" }, { - "name": "phenx/php-svg-lib", - "version": "0.5.0", + "name": "part-db/swap-bundle", + "version": "v6.1.0", "source": { "type": "git", - "url": "https://github.com/dompdf/php-svg-lib.git", - "reference": "76876c6cf3080bcb6f249d7d59705108166a6685" + "url": "https://github.com/Part-DB/symfony-swap.git", + "reference": "fd78ebfbd762b1d76b4d71f713f39add63dec62b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/76876c6cf3080bcb6f249d7d59705108166a6685", - "reference": "76876c6cf3080bcb6f249d7d59705108166a6685", + "url": "https://api.github.com/repos/Part-DB/symfony-swap/zipball/fd78ebfbd762b1d76b4d71f713f39add63dec62b", + "reference": "fd78ebfbd762b1d76b4d71f713f39add63dec62b", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": "^7.1 || ^8.0", - "sabberworm/php-css-parser": "^8.4" + "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": { - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" + "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" + } }, - "type": "library", "autoload": { "psr-4": { - "Svg\\": "src/Svg" + "Florianv\\SwapBundle\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0" + "MIT" ], "authors": [ { - "name": "Fabien Ménager", - "email": "fabien.menager@gmail.com" + "name": "Florian Voutzinos", + "email": "florian@voutzinos.com", + "homepage": "http://florian.voutzinos.com" + }, + { + "name": "Jan Böhmer", + "email": "mail@jan-boehmer.de" } ], - "description": "A library to read, parse and export to PDF SVG files.", - "homepage": "https://github.com/PhenX/php-svg-lib", + "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/dompdf/php-svg-lib/issues", - "source": "https://github.com/dompdf/php-svg-lib/tree/0.5.0" + "source": "https://github.com/Part-DB/symfony-swap/tree/v6.1.0" }, - "time": "2022-09-06T12:16:56+00:00" + "time": "2025-09-05T19:52:56+00:00" }, { "name": "php-http/discovery", - "version": "1.19.1", + "version": "1.20.0", "source": { "type": "git", "url": "https://github.com/php-http/discovery.git", - "reference": "57f3de01d32085fea20865f9b16fb0e69347c39e" + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/discovery/zipball/57f3de01d32085fea20865f9b16fb0e69347c39e", - "reference": "57f3de01d32085fea20865f9b16fb0e69347c39e", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", "shasum": "" }, "require": { @@ -4866,7 +8099,8 @@ "php-http/httplug": "^1.0 || ^2.0", "php-http/message-factory": "^1.0", "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", - "symfony/phpunit-bridge": "^6.2" + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" }, "type": "composer-plugin", "extra": { @@ -4905,22 +8139,22 @@ ], "support": { "issues": "https://github.com/php-http/discovery/issues", - "source": "https://github.com/php-http/discovery/tree/1.19.1" + "source": "https://github.com/php-http/discovery/tree/1.20.0" }, - "time": "2023-07-11T07:02:26+00:00" + "time": "2024-10-02T11:20:13+00:00" }, { "name": "php-http/httplug", - "version": "2.4.0", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/php-http/httplug.git", - "reference": "625ad742c360c8ac580fcc647a1541d29e257f67" + "reference": "5cad731844891a4c282f3f3e1b582c46839d22f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/httplug/zipball/625ad742c360c8ac580fcc647a1541d29e257f67", - "reference": "625ad742c360c8ac580fcc647a1541d29e257f67", + "url": "https://api.github.com/repos/php-http/httplug/zipball/5cad731844891a4c282f3f3e1b582c46839d22f4", + "reference": "5cad731844891a4c282f3f3e1b582c46839d22f4", "shasum": "" }, "require": { @@ -4962,9 +8196,9 @@ ], "support": { "issues": "https://github.com/php-http/httplug/issues", - "source": "https://github.com/php-http/httplug/tree/2.4.0" + "source": "https://github.com/php-http/httplug/tree/2.4.1" }, - "time": "2023-04-14T15:10:03+00:00" + "time": "2024-09-23T11:39:58+00:00" }, { "name": "php-http/message-factory", @@ -5023,31 +8257,26 @@ }, { "name": "php-http/promise", - "version": "1.1.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/php-http/promise.git", - "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88" + "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/promise/zipball/4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", - "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", + "url": "https://api.github.com/repos/php-http/promise/zipball/fc85b1fba37c169a69a07ef0d5a8075770cc1f83", + "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "friends-of-phpspec/phpspec-code-coverage": "^4.3.2", - "phpspec/phpspec": "^5.1.2 || ^6.2" + "friends-of-phpspec/phpspec-code-coverage": "^4.3.2 || ^6.3", + "phpspec/phpspec": "^5.1.2 || ^6.2 || ^7.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, "autoload": { "psr-4": { "Http\\Promise\\": "src/" @@ -5074,238 +8303,9 @@ ], "support": { "issues": "https://github.com/php-http/promise/issues", - "source": "https://github.com/php-http/promise/tree/1.1.0" + "source": "https://github.com/php-http/promise/tree/1.3.1" }, - "time": "2020-07-07T09:29:14+00:00" - }, - { - "name": "php-translation/common", - "version": "3.2.0", - "source": { - "type": "git", - "url": "https://github.com/php-translation/common.git", - "reference": "986ddf4e3b2b3458d2a7353658bd40764d8ca1d1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-translation/common/zipball/986ddf4e3b2b3458d2a7353658bd40764d8ca1d1", - "reference": "986ddf4e3b2b3458d2a7353658bd40764d8ca1d1", - "shasum": "" - }, - "require": { - "php": ">=7.2", - "symfony/translation": " ^3.4 || ^4.3 || ^5.0 || ^6.0" - }, - "require-dev": { - "phpunit/phpunit": "^8.4", - "symfony/phpunit-bridge": "^4.3 || ^5.0 || ^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Translation\\Common\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com" - } - ], - "description": "Common translation stuff", - "support": { - "issues": "https://github.com/php-translation/common/issues", - "source": "https://github.com/php-translation/common/tree/3.2.0" - }, - "time": "2022-02-04T11:49:38+00:00" - }, - { - "name": "php-translation/extractor", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/php-translation/extractor.git", - "reference": "09ad2f3654e6badb95a739b0284f5785531f7c8d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-translation/extractor/zipball/09ad2f3654e6badb95a739b0284f5785531f7c8d", - "reference": "09ad2f3654e6badb95a739b0284f5785531f7c8d", - "shasum": "" - }, - "require": { - "doctrine/annotations": "^1.7 || ^2.0", - "nikic/php-parser": "^3.0 || ^4.0", - "php": "^7.2 || ^8.0", - "symfony/finder": "^3.4 || ^4.4 || ^5.0 || ^6.0", - "twig/twig": "^2.0 || ^3.0" - }, - "require-dev": { - "knplabs/knp-menu": "^3.1", - "symfony/phpunit-bridge": "^5.0 || ^6.0", - "symfony/translation": "^3.4 || ^4.4 || ^5.0 || ^6.0", - "symfony/twig-bridge": "^3.4 || ^4.4 || ^5.0 || ^6.0", - "symfony/validator": "^3.4 || ^4.4 || ^5.0 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "psr-4": { - "Translation\\Extractor\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com" - } - ], - "description": "Extract translations form the source code", - "support": { - "issues": "https://github.com/php-translation/extractor/issues", - "source": "https://github.com/php-translation/extractor/tree/2.1.1" - }, - "time": "2023-03-28T11:37:22+00:00" - }, - { - "name": "php-translation/symfony-bundle", - "version": "0.14.0", - "source": { - "type": "git", - "url": "https://github.com/php-translation/symfony-bundle.git", - "reference": "1eb4d47c451eaae3efe2ca99b80b433cc4aeaf58" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-translation/symfony-bundle/zipball/1eb4d47c451eaae3efe2ca99b80b433cc4aeaf58", - "reference": "1eb4d47c451eaae3efe2ca99b80b433cc4aeaf58", - "shasum": "" - }, - "require": { - "nyholm/nsa": "^1.1", - "php": "^8.0", - "php-translation/extractor": "^2.0", - "php-translation/symfony-storage": "^2.1", - "symfony/asset": "^5.3 || ^6.0", - "symfony/console": "^5.3 || ^6.0", - "symfony/finder": "^5.3 || ^6.0", - "symfony/framework-bundle": "^5.3 || ^6.0", - "symfony/intl": "^5.3 || ^6.0", - "symfony/translation": "^5.3 || ^6.0", - "symfony/twig-bundle": "^5.3 || ^6.0", - "symfony/validator": "^5.3 || ^6.0", - "twig/twig": "^2.14.4 || ^3.3" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.3", - "matthiasnoback/symfony-config-test": "^4.1", - "matthiasnoback/symfony-dependency-injection-test": "^4.1", - "nyholm/psr7": "^1.1", - "nyholm/symfony-bundle-test": "^2.0", - "php-http/curl-client": "^1.7 || ^2.0", - "php-http/message": "^1.11", - "php-http/message-factory": "^1.0.2", - "php-translation/translator": "^1.0", - "symfony/dependency-injection": "^5.3 || ^6.0", - "symfony/phpunit-bridge": "^5.2 || ^6.0", - "symfony/twig-bridge": "^5.3 || ^6.0", - "symfony/web-profiler-bundle": "^5.3 || ^6.0" - }, - "suggest": { - "php-http/httplug-bundle": "To easier configure your httplug clients." - }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "0.12-dev" - } - }, - "autoload": { - "psr-4": { - "Translation\\Bundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com" - } - ], - "support": { - "issues": "https://github.com/php-translation/symfony-bundle/issues", - "source": "https://github.com/php-translation/symfony-bundle/tree/0.14.0" - }, - "time": "2023-07-11T17:58:52+00:00" - }, - { - "name": "php-translation/symfony-storage", - "version": "2.3.1", - "source": { - "type": "git", - "url": "https://github.com/php-translation/symfony-storage.git", - "reference": "95d52dd86d41fe0ec2c75e1469b5003956044cc8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-translation/symfony-storage/zipball/95d52dd86d41fe0ec2c75e1469b5003956044cc8", - "reference": "95d52dd86d41fe0ec2c75e1469b5003956044cc8", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "php-translation/common": "^3.0", - "symfony/translation": "^3.4 || ^4.2 || ^5.0 || ^6.0" - }, - "require-dev": { - "phpunit/phpunit": ">=8.5.20", - "symfony/framework-bundle": " ^3.4 || ^4.2 || ^5.0 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.4-dev" - } - }, - "autoload": { - "psr-4": { - "Translation\\SymfonyStorage\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com" - } - ], - "description": "A translation file storage using Symfony translation component.", - "support": { - "issues": "https://github.com/php-translation/symfony-storage/issues", - "source": "https://github.com/php-translation/symfony-storage/tree/2.3.1" - }, - "time": "2022-02-14T11:36:15+00:00" + "time": "2024-03-15T13:55:21+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -5362,28 +8362,35 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", + "version": "5.6.6", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.1", "ext-filter": "*", - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1 || ^2" }, "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" }, "type": "library", "extra": { @@ -5407,35 +8414,35 @@ }, { "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "email": "opensource@ijaap.nl" } ], "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.3.0" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.6" }, - "time": "2021-10-19T17:43:47+00:00" + "time": "2025-12-22T21:13:58+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.7.3", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419" + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", - "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195", + "reference": "92a98ada2b93d9b201a613cb5a33584dde25f195", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", - "php": "^7.4 || ^8.0", + "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.13" + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { "ext-tokenizer": "*", @@ -5471,36 +8478,142 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.3" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0" }, - "time": "2023-08-12T11:01:26+00:00" + "time": "2025-11-21T15:09:14+00:00" }, { - "name": "phpstan/phpdoc-parser", - "version": "1.23.1", + "name": "phpoffice/phpspreadsheet", + "version": "5.3.0", "source": { "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26" + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "4d597c1aacdde1805a33c525b9758113ea0d90df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/846ae76eef31c6d7790fac9bc399ecee45160b26", - "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/4d597c1aacdde1805a33c525b9758113ea0d90df", + "reference": "4d597c1aacdde1805a33c525b9758113ea0d90df", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "composer/pcre": "^1||^2||^3", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^8.1", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^2.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.5", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1 || ^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0", + "phpstan/phpstan-phpunit": "^1.0 || ^2.0", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions, required for NumberFormat Wizard", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.3.0" + }, + "time": "2025-11-24T15:47:10+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495", + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" }, "require-dev": { "doctrine/annotations": "^2.0", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^5.3.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", "symfony/process": "^5.2" }, "type": "library", @@ -5518,9 +8631,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.23.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0" }, - "time": "2023-08-03T16:32:59+00:00" + "time": "2025-08-30T15:50:23+00:00" }, { "name": "psr/cache", @@ -5724,16 +8837,16 @@ }, { "name": "psr/http-client", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { @@ -5770,26 +8883,26 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/1.0.2" + "source": "https://github.com/php-fig/http-client" }, - "time": "2023-04-10T20:12:12+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1", "psr/http-message": "^1.0 || ^2.0" }, "type": "library", @@ -5813,7 +8926,7 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -5825,22 +8938,22 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2023-04-10T20:10:41+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "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": { @@ -5849,7 +8962,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -5864,7 +8977,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "homepage": "http://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -5878,9 +8991,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", @@ -5940,16 +9053,16 @@ }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -5984,9 +9097,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "psr/simple-cache", @@ -6084,17 +9197,134 @@ "time": "2019-03-08T08:55:37+00:00" }, { - "name": "robrichards/xmlseclibs", - "version": "3.1.1", + "name": "revolt/event-loop", + "version": "v1.0.8", "source": { "type": "git", - "url": "https://github.com/robrichards/xmlseclibs.git", - "reference": "f8f19e58f26cdb42c54b214ff8a820760292f8df" + "url": "https://github.com/revoltphp/event-loop.git", + "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/f8f19e58f26cdb42c54b214ff8a820760292f8df", - "reference": "f8f19e58f26cdb42c54b214ff8a820760292f8df", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/b6fc06dce8e9b523c9946138fa5e62181934f91c", + "reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.15" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Revolt\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "ceesjank@gmail.com" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Rock-solid event loop for concurrent PHP applications.", + "keywords": [ + "async", + "asynchronous", + "concurrency", + "event", + "event-loop", + "non-blocking", + "scheduler" + ], + "support": { + "issues": "https://github.com/revoltphp/event-loop/issues", + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.8" + }, + "time": "2025-08-27T21:33:23+00:00" + }, + { + "name": "rhukster/dom-sanitizer", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/rhukster/dom-sanitizer.git", + "reference": "757e4d6ac03afe9afa4f97cbef453fc5c25f0729" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rhukster/dom-sanitizer/zipball/757e4d6ac03afe9afa4f97cbef453fc5c25f0729", + "reference": "757e4d6ac03afe9afa4f97cbef453fc5c25f0729", + "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.8" + }, + "time": "2024-04-15T08:48:55+00:00" + }, + { + "name": "robrichards/xmlseclibs", + "version": "3.1.4", + "source": { + "type": "git", + "url": "https://github.com/robrichards/xmlseclibs.git", + "reference": "bc87389224c6de95802b505e5265b0ec2c5bcdbd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/bc87389224c6de95802b505e5265b0ec2c5bcdbd", + "reference": "bc87389224c6de95802b505e5265b0ec2c5bcdbd", "shasum": "" }, "require": { @@ -6121,9 +9351,9 @@ ], "support": { "issues": "https://github.com/robrichards/xmlseclibs/issues", - "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.1" + "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.4" }, - "time": "2020-09-05T13:00:25+00:00" + "time": "2025-12-08T11:57:53+00:00" }, { "name": "s9e/regexp-builder", @@ -6169,24 +9399,26 @@ }, { "name": "s9e/sweetdom", - "version": "2.1.1", + "version": "3.4.1", "source": { "type": "git", "url": "https://github.com/s9e/SweetDOM.git", - "reference": "dd5d814f93621b1489bfbac8e0331122b928a18a" + "reference": "ef3a7d2745b30b4ad0d1d3d60be391a3604c69dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/SweetDOM/zipball/dd5d814f93621b1489bfbac8e0331122b928a18a", - "reference": "dd5d814f93621b1489bfbac8e0331122b928a18a", + "url": "https://api.github.com/repos/s9e/SweetDOM/zipball/ef3a7d2745b30b4ad0d1d3d60be391a3604c69dd", + "reference": "ef3a7d2745b30b4ad0d1d3d60be391a3604c69dd", "shasum": "" }, "require": { "ext-dom": "*", - "php": ">=8.0" + "php": "^8.1" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "friendsofphp/php-cs-fixer": "^3.52", + "phpunit/phpunit": "^10.0", + "s9e/repdoc": "dev-wip" }, "type": "library", "autoload": { @@ -6207,34 +9439,35 @@ ], "support": { "issues": "https://github.com/s9e/SweetDOM/issues", - "source": "https://github.com/s9e/SweetDOM/tree/2.1.1" + "source": "https://github.com/s9e/SweetDOM/tree/3.4.1" }, - "time": "2023-06-05T19:10:26+00:00" + "time": "2024-03-23T14:03:01+00:00" }, { "name": "s9e/text-formatter", - "version": "2.14.0", + "version": "2.19.3", "source": { "type": "git", "url": "https://github.com/s9e/TextFormatter.git", - "reference": "48a2f3a3fb18af8d78330204732a3369441c4060" + "reference": "aee579c12d05ca3053f9b9abdb8c479c0f2fbe69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/48a2f3a3fb18af8d78330204732a3369441c4060", - "reference": "48a2f3a3fb18af8d78330204732a3369441c4060", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/aee579c12d05ca3053f9b9abdb8c479c0f2fbe69", + "reference": "aee579c12d05ca3053f9b9abdb8c479c0f2fbe69", "shasum": "" }, "require": { "ext-dom": "*", "ext-filter": "*", "lib-pcre": ">=8.13", - "php": "^8.0", + "php": "^8.1", "s9e/regexp-builder": "^1.4", - "s9e/sweetdom": "^2.0" + "s9e/sweetdom": "^3.4" }, "require-dev": { "code-lts/doctum": "*", + "friendsofphp/php-cs-fixer": "^3.52", "matthiasmullie/minify": "*", "phpunit/phpunit": "^9.5" }, @@ -6249,7 +9482,7 @@ }, "type": "library", "extra": { - "version": "2.14.0" + "version": "2.19.3" }, "autoload": { "psr-4": { @@ -6281,36 +9514,49 @@ ], "support": { "issues": "https://github.com/s9e/TextFormatter/issues", - "source": "https://github.com/s9e/TextFormatter/tree/2.14.0" + "source": "https://github.com/s9e/TextFormatter/tree/2.19.3" }, - "time": "2023-06-08T07:19:50+00:00" + "time": "2025-11-14T21:26:59+00:00" }, { "name": "sabberworm/php-css-parser", - "version": "8.4.0", + "version": "v9.1.0", "source": { "type": "git", - "url": "https://github.com/sabberworm/PHP-CSS-Parser.git", - "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30" + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/e41d2140031d533348b2192a83f02d8dd8a71d30", - "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb", + "reference": "1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb", "shasum": "" }, "require": { "ext-iconv": "*", - "php": ">=5.6.20" + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.3" }, "require-dev": { - "codacy/coverage": "^1.4", - "phpunit/phpunit": "^4.8.36" + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/extension-installer": "1.4.3", + "phpstan/phpstan": "1.12.28 || 2.1.25", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.7", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6", + "phpunit/phpunit": "8.5.46", + "rawr/phpunit-data-provider": "3.3.1", + "rector/rector": "1.2.10 || 2.1.7", + "rector/type-perfect": "1.0.0 || 2.1.0" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.2.x-dev" + } + }, "autoload": { "psr-4": { "Sabberworm\\CSS\\": "src/" @@ -6323,6 +9569,14 @@ "authors": [ { "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" } ], "description": "Parser for CSS Files written in PHP", @@ -6333,27 +9587,27 @@ "stylesheet" ], "support": { - "issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues", - "source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.4.0" + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.1.0" }, - "time": "2021-12-11T13:40:54+00:00" + "time": "2025-09-14T07:37:21+00:00" }, { "name": "scheb/2fa-backup-code", - "version": "v6.9.0", + "version": "v7.13.1", "source": { "type": "git", "url": "https://github.com/scheb/2fa-backup-code.git", - "reference": "b01965cd221cda280526e48e7f56966154b9ba2f" + "reference": "35f1ace4be7be2c10158d2bb8284208499111db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-backup-code/zipball/b01965cd221cda280526e48e7f56966154b9ba2f", - "reference": "b01965cd221cda280526e48e7f56966154b9ba2f", + "url": "https://api.github.com/repos/scheb/2fa-backup-code/zipball/35f1ace4be7be2c10158d2bb8284208499111db8", + "reference": "35f1ace4be7be2c10158d2bb8284208499111db8", "shasum": "" }, "require": { - "php": "~8.0.0 || ~8.1.0 || ~8.2.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "scheb/2fa-bundle": "self.version" }, "type": "library", @@ -6383,36 +9637,37 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-backup-code/tree/v6.9.0" + "source": "https://github.com/scheb/2fa-backup-code/tree/v7.13.1" }, - "time": "2022-12-10T15:20:09+00:00" + "time": "2025-11-20T13:35:24+00:00" }, { "name": "scheb/2fa-bundle", - "version": "v6.9.0", + "version": "v7.13.1", "source": { "type": "git", "url": "https://github.com/scheb/2fa-bundle.git", - "reference": "98fee6bf6ce17514d8f3772d4c7f86e6f7595a85" + "reference": "edcc14456b508aab37ec792cfc36793d04226784" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-bundle/zipball/98fee6bf6ce17514d8f3772d4c7f86e6f7595a85", - "reference": "98fee6bf6ce17514d8f3772d4c7f86e6f7595a85", + "url": "https://api.github.com/repos/scheb/2fa-bundle/zipball/edcc14456b508aab37ec792cfc36793d04226784", + "reference": "edcc14456b508aab37ec792cfc36793d04226784", "shasum": "" }, "require": { "ext-json": "*", - "php": "~8.0.0 || ~8.1.0 || ~8.2.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 || ~8.5.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": "*" @@ -6450,29 +9705,31 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-bundle/tree/v6.9.0" + "source": "https://github.com/scheb/2fa-bundle/tree/v7.13.1" }, - "time": "2023-08-05T11:13:58+00:00" + "time": "2025-12-18T15:29:07+00:00" }, { "name": "scheb/2fa-google-authenticator", - "version": "v6.9.0", + "version": "v7.13.1", "source": { "type": "git", "url": "https://github.com/scheb/2fa-google-authenticator.git", - "reference": "20eab4c1814b587cac71c4516a06b192ca838294" + "reference": "7ad34bbde343a0770571464127ee072aacb70a58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-google-authenticator/zipball/20eab4c1814b587cac71c4516a06b192ca838294", - "reference": "20eab4c1814b587cac71c4516a06b192ca838294", + "url": "https://api.github.com/repos/scheb/2fa-google-authenticator/zipball/7ad34bbde343a0770571464127ee072aacb70a58", + "reference": "7ad34bbde343a0770571464127ee072aacb70a58", "shasum": "" }, "require": { - "paragonie/constant_time_encoding": "^2.4", - "php": "~8.0.0 || ~8.1.0 || ~8.2.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "scheb/2fa-bundle": "self.version", - "spomky-labs/otphp": "^10.0 || ^11.0" + "spomky-labs/otphp": "^11.0" + }, + "suggest": { + "symfony/validator": "Needed if you want to use the Google Authenticator TOTP validator constraint" }, "type": "library", "autoload": { @@ -6501,28 +9758,28 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-google-authenticator/tree/v6.9.0" + "source": "https://github.com/scheb/2fa-google-authenticator/tree/v7.13.1" }, - "time": "2022-12-10T15:20:09+00:00" + "time": "2025-12-04T15:55:14+00:00" }, { "name": "scheb/2fa-trusted-device", - "version": "v6.9.0", + "version": "v7.13.1", "source": { "type": "git", "url": "https://github.com/scheb/2fa-trusted-device.git", - "reference": "cac6feaf9f2c7d3a1aade86942f7b7b234fcd151" + "reference": "ae3a5819faccbf151af078f432e4e6c97bb44ebf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-trusted-device/zipball/cac6feaf9f2c7d3a1aade86942f7b7b234fcd151", - "reference": "cac6feaf9f2c7d3a1aade86942f7b7b234fcd151", + "url": "https://api.github.com/repos/scheb/2fa-trusted-device/zipball/ae3a5819faccbf151af078f432e4e6c97bb44ebf", + "reference": "ae3a5819faccbf151af078f432e4e6c97bb44ebf", "shasum": "" }, "require": { - "lcobucci/clock": "^2.0 || ^3.0", - "lcobucci/jwt": "^4.1 || ^5.0", - "php": "~8.0.0 || ~8.1.0 || ~8.2.0", + "lcobucci/clock": "^3.0", + "lcobucci/jwt": "^5.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "scheb/2fa-bundle": "self.version" }, "type": "library", @@ -6552,36 +9809,36 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-trusted-device/tree/v6.9.0" + "source": "https://github.com/scheb/2fa-trusted-device/tree/v7.13.1" }, - "time": "2023-04-01T11:20:00+00:00" + "time": "2025-12-01T15:40:59+00:00" }, { "name": "shivas/versioning-bundle", - "version": "4.0.3", + "version": "4.1.1", "source": { "type": "git", "url": "https://github.com/shivas/versioning-bundle.git", - "reference": "88d8fe08f7be8ce7b802adecc71a90f40b7bb8e4" + "reference": "fd89e3501ff1b0d3e6abe61eb7a878d1d4746868" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shivas/versioning-bundle/zipball/88d8fe08f7be8ce7b802adecc71a90f40b7bb8e4", - "reference": "88d8fe08f7be8ce7b802adecc71a90f40b7bb8e4", + "url": "https://api.github.com/repos/shivas/versioning-bundle/zipball/fd89e3501ff1b0d3e6abe61eb7a878d1d4746868", + "reference": "fd89e3501ff1b0d3e6abe61eb7a878d1d4746868", "shasum": "" }, "require": { "nikolaposa/version": "^4", - "php": "^7.2 || ^8", - "symfony/console": "^3.4 || ^4 || ^5 || ^6", - "symfony/framework-bundle": "^3.4 || ^4 || ^5 || ^6", - "symfony/process": "^3.4 || ^4 || ^5 || ^6" + "php": "^7.2.5 || ^8", + "symfony/console": "^5.4 || ^6 || ^7", + "symfony/framework-bundle": "^5.4 || ^6 || ^7", + "symfony/process": "^5.4 || ^6 || ^7" }, "require-dev": { "mikey179/vfsstream": "^2", - "nyholm/symfony-bundle-test": "1.x-dev", + "nyholm/symfony-bundle-test": "^3.0", "phpunit/phpunit": "^8.5.27", - "symfony/phpunit-bridge": "^5 || ^6", + "symfony/phpunit-bridge": "^5.4 || ^6 || ^7", "twig/twig": "^2 || ^3" }, "type": "symfony-bundle", @@ -6611,28 +9868,28 @@ ], "support": { "issues": "https://github.com/shivas/versioning-bundle/issues", - "source": "https://github.com/shivas/versioning-bundle/tree/4.0.3", + "source": "https://github.com/shivas/versioning-bundle/tree/4.1.1", "wiki": "https://github.com/shivas/versioning-bundle/wiki" }, - "time": "2023-07-30T17:15:29+00:00" + "time": "2024-08-14T19:33:15+00:00" }, { "name": "spatie/db-dumper", - "version": "3.4.0", + "version": "3.8.2", "source": { "type": "git", "url": "https://github.com/spatie/db-dumper.git", - "reference": "bbd5ae0f331d47e6534eb307e256c11a65c8e24a" + "reference": "9519c64e4938f0b9e4498b8a8e572061bc6b7cfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/db-dumper/zipball/bbd5ae0f331d47e6534eb307e256c11a65c8e24a", - "reference": "bbd5ae0f331d47e6534eb307e256c11a65c8e24a", + "url": "https://api.github.com/repos/spatie/db-dumper/zipball/9519c64e4938f0b9e4498b8a8e572061bc6b7cfb", + "reference": "9519c64e4938f0b9e4498b8a8e572061bc6b7cfb", "shasum": "" }, "require": { "php": "^8.0", - "symfony/process": "^5.0|^6.0" + "symfony/process": "^5.0|^6.0|^7.0|^8.0" }, "require-dev": { "pestphp/pest": "^1.22" @@ -6665,7 +9922,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/db-dumper/tree/3.4.0" + "source": "https://github.com/spatie/db-dumper/tree/3.8.2" }, "funding": [ { @@ -6677,124 +9934,32 @@ "type": "github" } ], - "time": "2023-06-27T08:34:52+00:00" - }, - { - "name": "spomky-labs/cbor-bundle", - "version": "v3.0.0", - "source": { - "type": "git", - "url": "https://github.com/Spomky-Labs/cbor-bundle.git", - "reference": "157ca6ed2f6e957f9e95d71ca86bc67bf42ee79c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/cbor-bundle/zipball/157ca6ed2f6e957f9e95d71ca86bc67bf42ee79c", - "reference": "157ca6ed2f6e957f9e95d71ca86bc67bf42ee79c", - "shasum": "" - }, - "require": { - "php": ">=8.0", - "spomky-labs/cbor-php": "^3.0", - "symfony/config": "^5.3|^6.0", - "symfony/dependency-injection": "^5.3|^6.0", - "symfony/http-kernel": "^5.3|^6.0" - }, - "require-dev": { - "infection/infection": "^0.25.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": "^9.0", - "rector/rector": "^0.12.5", - "symfony/framework-bundle": "^5.3|^6.0", - "symfony/phpunit-bridge": "^5.3|^6.0", - "symplify/easy-coding-standard": "^9.4" - }, - "type": "symfony-bundle", - "autoload": { - "psr-4": { - "SpomkyLabs\\CborBundle\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Florent Morselli", - "homepage": "https://github.com/Spomky" - }, - { - "name": "All contributors", - "homepage": "https://github.com/spomky-labs/cbor-bundle/contributors" - } - ], - "description": "CBOR Encoder/Decoder Bundle for Symfony.", - "homepage": "https://github.com/spomky-labs", - "keywords": [ - "Concise Binary Object Representation", - "RFC7049", - "bundle", - "cbor", - "symfony" - ], - "support": { - "issues": "https://github.com/Spomky-Labs/cbor-bundle/issues", - "source": "https://github.com/Spomky-Labs/cbor-bundle/tree/v3.0.0" - }, - "funding": [ - { - "url": "https://github.com/Spomky", - "type": "github" - }, - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "time": "2021-11-23T21:41:00+00:00" + "time": "2025-12-10T09:29:52+00:00" }, { "name": "spomky-labs/cbor-php", - "version": "3.0.2", + "version": "3.2.2", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/cbor-php.git", - "reference": "81d5dff7a1101d680729b5789f4359d01b15e6c5" + "reference": "2a5fb86aacfe1004611370ead6caa2bfc88435d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/81d5dff7a1101d680729b5789f4359d01b15e6c5", - "reference": "81d5dff7a1101d680729b5789f4359d01b15e6c5", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/2a5fb86aacfe1004611370ead6caa2bfc88435d0", + "reference": "2a5fb86aacfe1004611370ead6caa2bfc88435d0", "shasum": "" }, "require": { - "brick/math": "^0.9|^0.10|^0.11", + "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13|^0.14", "ext-mbstring": "*", "php": ">=8.0" }, "require-dev": { - "ekino/phpstan-banned-code": "^1.0", "ext-json": "*", - "infection/infection": "^0.26", - "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.0", - "qossmic/deptrac-shim": "^1.0", - "rector/rector": "^0.15", "roave/security-advisories": "dev-latest", - "symfony/var-dumper": "^6.0", - "symplify/easy-coding-standard": "^11.1" + "symfony/error-handler": "^6.4|^7.1|^8.0", + "symfony/var-dumper": "^6.4|^7.1|^8.0" }, "suggest": { "ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags", @@ -6828,7 +9993,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/cbor-php/issues", - "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.0.2" + "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.2.2" }, "funding": [ { @@ -6840,40 +10005,42 @@ "type": "patreon" } ], - "time": "2023-02-28T21:37:12+00:00" + "time": "2025-11-13T13:00:34+00:00" }, { "name": "spomky-labs/otphp", - "version": "11.2.0", + "version": "11.3.0", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/otphp.git", - "reference": "9a1569038bb1c8e98040b14b8bcbba54f25e7795" + "reference": "2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/9a1569038bb1c8e98040b14b8bcbba54f25e7795", - "reference": "9a1569038bb1c8e98040b14b8bcbba54f25e7795", + "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33", + "reference": "2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33", "shasum": "" }, "require": { "ext-mbstring": "*", - "paragonie/constant_time_encoding": "^2.0", - "php": "^8.1" + "paragonie/constant_time_encoding": "^2.0 || ^3.0", + "php": ">=8.1", + "psr/clock": "^1.0", + "symfony/deprecation-contracts": "^3.2" }, "require-dev": { "ekino/phpstan-banned-code": "^1.0", - "infection/infection": "^0.26", + "infection/infection": "^0.26|^0.27|^0.28|^0.29", "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/phpstan": "^1.0", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5.26", + "phpunit/phpunit": "^9.5.26|^10.0|^11.0", "qossmic/deptrac-shim": "^1.0", - "rector/rector": "^0.15", - "symfony/phpunit-bridge": "^6.1", - "symplify/easy-coding-standard": "^11.0" + "rector/rector": "^1.0", + "symfony/phpunit-bridge": "^6.1|^7.0", + "symplify/easy-coding-standard": "^12.0" }, "type": "library", "autoload": { @@ -6908,7 +10075,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/otphp/issues", - "source": "https://github.com/Spomky-Labs/otphp/tree/11.2.0" + "source": "https://github.com/Spomky-Labs/otphp/tree/11.3.0" }, "funding": [ { @@ -6920,45 +10087,44 @@ "type": "patreon" } ], - "time": "2023-03-16T19:16:25+00:00" + "time": "2024-06-12T11:22:32+00:00" }, { "name": "spomky-labs/pki-framework", - "version": "1.1.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "d3ba688bf40e7c6e0dabf065ee18fc210734e760" + "reference": "f0e9a548df4e3942886adc9b7830581a46334631" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/d3ba688bf40e7c6e0dabf065ee18fc210734e760", - "reference": "d3ba688bf40e7c6e0dabf065ee18fc210734e760", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/f0e9a548df4e3942886adc9b7830581a46334631", + "reference": "f0e9a548df4e3942886adc9b7830581a46334631", "shasum": "" }, "require": { - "brick/math": "^0.10 || ^0.11", + "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14", "ext-mbstring": "*", "php": ">=8.1" }, "require-dev": { - "ekino/phpstan-banned-code": "^1.0", + "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", "ext-gmp": "*", "ext-openssl": "*", - "infection/infection": "^0.26", + "infection/infection": "^0.28|^0.29|^0.31", "php-parallel-lint/php-parallel-lint": "^1.3", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-beberlei-assert": "^1.0", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^10.0", - "rector/rector": "^0.15", + "phpstan/extension-installer": "^1.3|^2.0", + "phpstan/phpstan": "^1.8|^2.0", + "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|^12.0", + "rector/rector": "^1.0|^2.0", "roave/security-advisories": "dev-latest", - "symfony/phpunit-bridge": "^6.1", - "symfony/var-dumper": "^6.1", - "symplify/easy-coding-standard": "^11.1", - "thecodingmachine/phpstan-safe-rule": "^1.2" + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symplify/easy-coding-standard": "^12.0" }, "suggest": { "ext-bcmath": "For better performance (or GMP)", @@ -7018,7 +10184,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.1.0" + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.1" }, "funding": [ { @@ -7030,7 +10196,7 @@ "type": "patreon" } ], - "time": "2023-02-13T17:21:24+00:00" + "time": "2025-12-20T12:57:40+00:00" }, { "name": "symfony/apache-pack", @@ -7060,28 +10226,28 @@ }, { "name": "symfony/asset", - "version": "v6.3.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/asset.git", - "reference": "b77a4cc8e266b7e0db688de740f9ee7253aa411c" + "reference": "0f7bccb9ffa1f373cbd659774d90629b2773464f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset/zipball/b77a4cc8e266b7e0db688de740f9ee7253aa411c", - "reference": "b77a4cc8e266b7e0db688de740f9ee7253aa411c", + "url": "https://api.github.com/repos/symfony/asset/zipball/0f7bccb9ffa1f373cbd659774d90629b2773464f", + "reference": "0f7bccb9ffa1f373cbd659774d90629b2773464f", "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", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0" + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -7109,7 +10275,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.3.0" + "source": "https://github.com/symfony/asset/tree/v7.4.0" }, "funding": [ { @@ -7120,40 +10286,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": "2023-04-21T14:41:17+00:00" + "time": "2025-08-04T07:05:15+00:00" }, { "name": "symfony/cache", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "d176b97600860df13e851538c2df2ad88e9e5ca9" + "reference": "642117d18bc56832e74b68235359ccefab03dd11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/d176b97600860df13e851538c2df2ad88e9e5ca9", - "reference": "d176b97600860df13e851538c2df2ad88e9e5ca9", + "url": "https://api.github.com/repos/symfony/cache/zipball/642117d18bc56832e74b68235359ccefab03dd11", + "reference": "642117d18bc56832e74b68235359ccefab03dd11", "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", "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^6.2.10" + "symfony/var-exporter": "^6.4|^7.0|^8.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", + "ext-redis": "<6.1", + "ext-relay": "<0.12.1", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" }, "provide": { "psr/cache-implementation": "2.0|3.0", @@ -7162,15 +10335,16 @@ }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/dbal": "^2.13.1|^3.0", + "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", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/filesystem": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -7205,7 +10379,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.3.2" + "source": "https://github.com/symfony/cache/tree/v7.4.3" }, "funding": [ { @@ -7216,25 +10390,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": "2023-07-27T16:19:14+00:00" + "time": "2025-12-28T10:45:24+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.3.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "ad945640ccc0ae6e208bcea7d7de4b39b569896b" + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/ad945640ccc0ae6e208bcea7d7de4b39b569896b", - "reference": "ad945640ccc0ae6e208bcea7d7de4b39b569896b", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", "shasum": "" }, "require": { @@ -7243,12 +10421,12 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -7281,7 +10459,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0" }, "funding": [ { @@ -7297,25 +10475,26 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2025-03-13T15:25:07+00:00" }, { "name": "symfony/clock", - "version": "v6.3.1", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "2c72817f85cbdd0ae4e49643514a889004934296" + "reference": "9169f24776edde469914c1e7a1442a50f7a4e110" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/2c72817f85cbdd0ae4e49643514a889004934296", - "reference": "2c72817f85cbdd0ae4e49643514a889004934296", + "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110", + "reference": "9169f24776edde469914c1e7a1442a50f7a4e110", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/clock": "^1.0" + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" }, "provide": { "psr/clock-implementation": "1.0" @@ -7354,7 +10533,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v6.3.1" + "source": "https://github.com/symfony/clock/tree/v7.4.0" }, "funding": [ { @@ -7365,43 +10544,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": "2023-06-08T23:46:55+00:00" + "time": "2025-11-12T15:39:26+00:00" }, { "name": "symfony/config", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "b47ca238b03e7b0d7880ffd1cf06e8d637ca1467" + "reference": "800ce889e358a53a9678b3212b0c8cecd8c6aace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/b47ca238b03e7b0d7880ffd1cf06e8d637ca1467", - "reference": "b47ca238b03e7b0d7880ffd1cf06e8d637ca1467", + "url": "https://api.github.com/repos/symfony/config/zipball/800ce889e358a53a9678b3212b0c8cecd8c6aace", + "reference": "800ce889e358a53a9678b3212b0c8cecd8c6aace", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^5.4|^6.0", + "symfony/filesystem": "^7.1|^8.0", "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", - "symfony/finder": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0" + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -7429,7 +10612,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.3.2" + "source": "https://github.com/symfony/config/tree/v7.4.3" }, "funding": [ { @@ -7440,52 +10623,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": "2023-07-19T20:22:16+00:00" + "time": "2025-12-23T14:24:27+00:00" }, { "name": "symfony/console", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898" + "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/aa5d64ad3f63f2e48964fc81ee45cb318a723898", - "reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898", + "url": "https://api.github.com/repos/symfony/console/zipball/732a9ca6cd9dfd940c639062d5edbde2f6727fb6", + "reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6", "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" + "symfony/string": "^7.2|^8.0" }, "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", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/lock": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -7519,7 +10710,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.3.2" + "source": "https://github.com/symfony/console/tree/v7.4.3" }, "funding": [ { @@ -7530,29 +10721,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": "2023-07-19T20:17:28+00:00" + "time": "2025-12-23T14:50:43+00:00" }, { "name": "symfony/css-selector", - "version": "v6.3.2", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "883d961421ab1709877c10ac99451632a3d6fa57" + "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/883d961421ab1709877c10ac99451632a3d6fa57", - "reference": "883d961421ab1709877c10ac99451632a3d6fa57", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", "autoload": { @@ -7584,7 +10779,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.3.2" + "source": "https://github.com/symfony/css-selector/tree/v7.4.0" }, "funding": [ { @@ -7595,49 +10790,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": "2023-07-12T16:00:22+00:00" + "time": "2025-10-30T13:39:42+00:00" }, { "name": "symfony/dependency-injection", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "474cfbc46aba85a1ca11a27db684480d0db64ba7" + "reference": "54122901b6d772e94f1e71a75e0533bc16563499" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/474cfbc46aba85a1ca11a27db684480d0db64ba7", - "reference": "474cfbc46aba85a1ca11a27db684480d0db64ba7", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/54122901b6d772e94f1e71a75e0533bc16563499", + "reference": "54122901b6d772e94f1e71a75e0533bc16563499", "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" + "symfony/service-contracts": "^3.6", + "symfony/var-exporter": "^6.4.20|^7.2.5|^8.0" }, "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", - "symfony/expression-language": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0" + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -7665,7 +10863,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.3.2" + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.3" }, "funding": [ { @@ -7676,25 +10874,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": "2023-07-19T20:17:28+00:00" + "time": "2025-12-28T10:55:46+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.3.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -7702,12 +10904,12 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -7732,7 +10934,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -7748,73 +10950,72 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/doctrine-bridge", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "61c7d16fbb61ffe3a08d1b965355d6b1006c3594" + "reference": "bd338ba08f5c47fe77e0a15e85ec3c5d070f9ceb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/61c7d16fbb61ffe3a08d1b965355d6b1006c3594", - "reference": "61c7d16fbb61ffe3a08d1b965355d6b1006c3594", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/bd338ba08f5c47fe77e0a15e85ec3c5d070f9ceb", + "reference": "bd338ba08f5c47fe77e0a15e85ec3c5d070f9ceb", "shasum": "" }, "require": { - "doctrine/event-manager": "^1.2|^2", - "doctrine/persistence": "^2|^3", - "php": ">=8.1", + "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/annotations": "<1.13.1", - "doctrine/dbal": "<2.13.1", + "doctrine/collections": "<1.8", + "doctrine/dbal": "<3.6", "doctrine/lexer": "<1.1", - "doctrine/orm": "<2.12", - "symfony/cache": "<5.4", - "symfony/dependency-injection": "<6.2", - "symfony/form": "<5.4.21|>=6,<6.2.7", - "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.0", - "symfony/validator": "<5.4.25|>=6,<6.2.12|>=6.3,<6.3.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": "<7.4" }, "require-dev": { - "doctrine/annotations": "^1.13.1|^2", - "doctrine/collections": "^1.0|^2.0", - "doctrine/data-fixtures": "^1.1", - "doctrine/dbal": "^2.13.1|^3.0", - "doctrine/orm": "^2.12", + "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": "^5.4|^6.0", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^6.2", - "symfony/doctrine-messenger": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/form": "^5.4.21|^6.2.7", - "symfony/http-kernel": "^6.3", - "symfony/lock": "^6.3", - "symfony/messenger": "^5.4|^6.0", - "symfony/property-access": "^5.4|^6.0", - "symfony/property-info": "^5.4|^6.0", - "symfony/proxy-manager-bridge": "^5.4|^6.0", - "symfony/security-core": "^6.0", - "symfony/stopwatch": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "symfony/validator": "^5.4.25|~6.2.12|^6.3.1", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/doctrine-messenger": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/form": "^7.2|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/security-core": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/type-info": "^7.1.8|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "symfony-bridge", "autoload": { @@ -7842,7 +11043,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v6.3.2" + "source": "https://github.com/symfony/doctrine-bridge/tree/v7.4.3" }, "funding": [ { @@ -7853,37 +11054,113 @@ "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": "2023-07-20T14:51:28+00:00" + "time": "2025-12-22T13:47:05+00:00" }, { - "name": "symfony/dotenv", - "version": "v6.3.0", + "name": "symfony/dom-crawler", + "version": "v7.4.1", "source": { "type": "git", - "url": "https://github.com/symfony/dotenv.git", - "reference": "ceadb434fe2a6763a03d2d110441745834f3dd1e" + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "0c5e8f20c74c78172a8ee72b125909b505033597" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/ceadb434fe2a6763a03d2d110441745834f3dd1e", - "reference": "ceadb434fe2a6763a03d2d110441745834f3dd1e", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/0c5e8f20c74c78172a8ee72b125909b505033597", + "reference": "0c5e8f20c74c78172a8ee72b125909b505033597", "shasum": "" }, "require": { - "php": ">=8.1" - }, - "conflict": { - "symfony/console": "<5.4", - "symfony/process": "<5.4" + "masterminds/html5": "^2.6", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0" + "symfony/css-selector": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v7.4.1" + }, + "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-12-06T15:47:47+00:00" + }, + { + "name": "symfony/dotenv", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "1658a4d34df028f3d93bcdd8e81f04423925a364" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/1658a4d34df028f3d93bcdd8e81f04423925a364", + "reference": "1658a4d34df028f3d93bcdd8e81f04423925a364", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/process": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -7916,7 +11193,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v6.3.0" + "source": "https://github.com/symfony/dotenv/tree/v7.4.0" }, "funding": [ { @@ -7927,39 +11204,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": "2023-04-21T14:41:17+00:00" + "time": "2025-11-16T10:14:42+00:00" }, { "name": "symfony/error-handler", - "version": "v6.3.2", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "85fd65ed295c4078367c784e8a5a6cee30348b7a" + "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/85fd65ed295c4078367c784e8a5a6cee30348b7a", - "reference": "85fd65ed295c4078367c784e8a5a6cee30348b7a", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/48be2b0653594eea32dcef130cca1c811dcf25c2", + "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "conflict": { - "symfony/deprecation-contracts": "<2.5" + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" }, "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0" + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -7990,7 +11275,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.3.2" + "source": "https://github.com/symfony/error-handler/tree/v7.4.0" }, "funding": [ { @@ -8001,33 +11286,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": "2023-07-16T17:05:46+00:00" + "time": "2025-11-05T14:29:59+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.3.2", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e" + "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/adb01fe097a4ee930db9258a3cc906b5beb5cf2e", - "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9dddcddff1ef974ad87b3708e4b442dc38b2261d", + "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d", "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": { @@ -8036,13 +11325,14 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/error-handler": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^5.4|^6.0" + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -8070,7 +11360,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.3.2" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.0" }, "funding": [ { @@ -8081,25 +11371,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": "2023-07-06T06:56:43+00:00" + "time": "2025-10-28T09:38:46+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.3.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", - "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { @@ -8108,12 +11402,12 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -8146,7 +11440,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -8162,25 +11456,25 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/expression-language", - "version": "v6.3.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/expression-language.git", - "reference": "6d560c4c80e7e328708efd923f93ad67e6a0c1c0" + "reference": "8b9bbbb8c71f79a09638f6ea77c531e511139efa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/6d560c4c80e7e328708efd923f93ad67e6a0c1c0", - "reference": "6d560c4c80e7e328708efd923f93ad67e6a0c1c0", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/8b9bbbb8c71f79a09638f6ea77c531e511139efa", + "reference": "8b9bbbb8c71f79a09638f6ea77c531e511139efa", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/cache": "^5.4|^6.0", + "php": ">=8.2", + "symfony/cache": "^6.4|^7.0|^8.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/service-contracts": "^2.5|^3" }, @@ -8210,7 +11504,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.3.0" + "source": "https://github.com/symfony/expression-language/tree/v7.4.0" }, "funding": [ { @@ -8221,32 +11515,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": "2023-04-28T16:05:33+00:00" + "time": "2025-11-12T15:39:26+00:00" }, { "name": "symfony/filesystem", - "version": "v6.3.1", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae" + "reference": "d551b38811096d0be9c4691d406991b47c0c630a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/edd36776956f2a6fcf577edb5b05eb0e3bdc52ae", - "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", + "reference": "d551b38811096d0be9c4691d406991b47c0c630a", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, + "require-dev": { + "symfony/process": "^6.4|^7.0|^8.0" + }, "type": "library", "autoload": { "psr-4": { @@ -8273,7 +11574,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.3.1" + "source": "https://github.com/symfony/filesystem/tree/v7.4.0" }, "funding": [ { @@ -8284,32 +11585,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": "2023-06-01T08:30:39+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "symfony/finder", - "version": "v6.3.3", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e" + "reference": "fffe05569336549b20a1be64250b40516d6e8d06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9915db259f67d21eefee768c1abcf1cc61b1fc9e", - "reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e", + "url": "https://api.github.com/repos/symfony/finder/zipball/fffe05569336549b20a1be64250b40516d6e8d06", + "reference": "fffe05569336549b20a1be64250b40516d6e8d06", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "symfony/filesystem": "^6.0" + "symfony/filesystem": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -8337,7 +11642,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.3.3" + "source": "https://github.com/symfony/finder/tree/v7.4.3" }, "funding": [ { @@ -8348,37 +11653,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": "2023-07-31T08:31:44+00:00" + "time": "2025-12-23T14:50:43+00:00" }, { "name": "symfony/flex", - "version": "v2.3.3", + "version": "v2.10.0", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "9c402af768c6c9f8126a9ffa192ecf7c16581e35" + "reference": "9cd384775973eabbf6e8b05784dda279fc67c28d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/9c402af768c6c9f8126a9ffa192ecf7c16581e35", - "reference": "9c402af768c6c9f8126a9ffa192ecf7c16581e35", + "url": "https://api.github.com/repos/symfony/flex/zipball/9cd384775973eabbf6e8b05784dda279fc67c28d", + "reference": "9cd384775973eabbf6e8b05784dda279fc67c28d", "shasum": "" }, "require": { "composer-plugin-api": "^2.1", - "php": ">=8.0" + "php": ">=8.1" + }, + "conflict": { + "composer/semver": "<1.7.2", + "symfony/dotenv": "<5.4" }, "require-dev": { "composer/composer": "^2.1", - "symfony/dotenv": "^5.4|^6.0", - "symfony/filesystem": "^5.4|^6.0", - "symfony/phpunit-bridge": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0" + "symfony/dotenv": "^6.4|^7.4|^8.0", + "symfony/filesystem": "^6.4|^7.4|^8.0", + "symfony/phpunit-bridge": "^6.4|^7.4|^8.0", + "symfony/process": "^6.4|^7.4|^8.0" }, "type": "composer-plugin", "extra": { @@ -8402,7 +11715,7 @@ "description": "Composer plugin for Symfony", "support": { "issues": "https://github.com/symfony/flex/issues", - "source": "https://github.com/symfony/flex/tree/v2.3.3" + "source": "https://github.com/symfony/flex/tree/v2.10.0" }, "funding": [ { @@ -8413,65 +11726,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": "2023-08-04T09:02:35+00:00" + "time": "2025-11-16T09:38:19+00:00" }, { "name": "symfony/form", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "afdadf511e08bc6d4752afb869ce084276aca4e2" + "reference": "f7e147d3e57198122568f17909bc85266b0b2592" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/afdadf511e08bc6d4752afb869ce084276aca4e2", - "reference": "afdadf511e08bc6d4752afb869ce084276aca4e2", + "url": "https://api.github.com/repos/symfony/form/zipball/f7e147d3e57198122568f17909bc85266b0b2592", + "reference": "f7e147d3e57198122568f17909bc85266b0b2592", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/options-resolver": "^5.4|^6.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/options-resolver": "^7.3|^8.0", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-icu": "^1.21", "symfony/polyfill-mbstring": "~1.0", - "symfony/property-access": "^5.4|^6.0", + "symfony/property-access": "^6.4|^7.0|^8.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", + "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/intl": "<7.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", - "symfony/console": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/html-sanitizer": "^6.1", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/intl": "^5.4|^6.0", - "symfony/security-core": "^6.2", - "symfony/security-csrf": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "symfony/validator": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/html-sanitizer": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/security-core": "^6.4|^7.0|^8.0", + "symfony/security-csrf": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4.3|^7.0.3|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4.12|^7.1.5|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -8499,7 +11818,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v6.3.2" + "source": "https://github.com/symfony/form/tree/v7.4.3" }, "funding": [ { @@ -8510,112 +11829,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": "2023-07-26T17:39:03+00:00" + "time": "2025-12-23T14:50:43+00:00" }, { "name": "symfony/framework-bundle", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "930fe7ee25a928b9b3627d717873ddd171430a82" + "reference": "df908e8f9e5f6cc3c9e0d0172e030a5c1c280582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/930fe7ee25a928b9b3627d717873ddd171430a82", - "reference": "930fe7ee25a928b9b3627d717873ddd171430a82", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/df908e8f9e5f6cc3c9e0d0172e030a5c1c280582", + "reference": "df908e8f9e5f6cc3c9e0d0172e030a5c1c280582", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=8.1", - "symfony/cache": "^5.4|^6.0", - "symfony/config": "^6.1", - "symfony/dependency-injection": "^6.3.1", + "php": ">=8.2", + "symfony/cache": "^6.4.12|^7.0|^8.0", + "symfony/config": "^7.4.3|^8.0.3", + "symfony/dependency-injection": "^7.4|^8.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^6.1", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/filesystem": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", - "symfony/http-foundation": "^6.3", - "symfony/http-kernel": "^6.3", + "symfony/error-handler": "^7.3|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^7.1|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", "symfony/polyfill-mbstring": "~1.0", - "symfony/routing": "^5.4|^6.0" + "symfony/polyfill-php85": "^1.32", + "symfony/routing": "^7.4|^8.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/clock": "<6.3", - "symfony/console": "<5.4", - "symfony/dom-crawler": "<6.3", - "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/mime": "<6.2", - "symfony/property-access": "<5.4", - "symfony/property-info": "<5.4", - "symfony/security-core": "<5.4", - "symfony/security-csrf": "<5.4", - "symfony/serializer": "<6.3", - "symfony/stopwatch": "<5.4", - "symfony/translation": "<6.2.8", - "symfony/twig-bridge": "<5.4", - "symfony/twig-bundle": "<5.4", - "symfony/validator": "<6.3", - "symfony/web-profiler-bundle": "<5.4", - "symfony/workflow": "<5.4" + "symfony/asset": "<6.4", + "symfony/asset-mapper": "<6.4", + "symfony/clock": "<6.4", + "symfony/console": "<6.4", + "symfony/dom-crawler": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/form": "<7.4", + "symfony/http-client": "<6.4", + "symfony/lock": "<6.4", + "symfony/mailer": "<6.4", + "symfony/messenger": "<7.4", + "symfony/mime": "<6.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": "<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/webhook": "<7.2", + "symfony/workflow": "<7.4" }, "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", - "symfony/asset": "^5.4|^6.0", - "symfony/asset-mapper": "^6.3", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/clock": "^6.2", - "symfony/console": "^5.4.9|^6.0.9", - "symfony/css-selector": "^5.4|^6.0", - "symfony/dom-crawler": "^6.3", - "symfony/dotenv": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/form": "^5.4|^6.0", - "symfony/html-sanitizer": "^6.1", - "symfony/http-client": "^6.3", - "symfony/lock": "^5.4|^6.0", - "symfony/mailer": "^5.4|^6.0", - "symfony/messenger": "^6.3", - "symfony/mime": "^6.2", - "symfony/notifier": "^5.4|^6.0", + "seld/jsonlint": "^1.10", + "symfony/asset": "^6.4|^7.0|^8.0", + "symfony/asset-mapper": "^6.4|^7.0|^8.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/dotenv": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/html-sanitizer": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/json-streamer": "^7.3|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/mailer": "^6.4|^7.0|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/notifier": "^6.4|^7.0|^8.0", + "symfony/object-mapper": "^7.3|^8.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "^5.4|^6.0", - "symfony/property-info": "^5.4|^6.0", - "symfony/rate-limiter": "^5.4|^6.0", - "symfony/scheduler": "^6.3", - "symfony/security-bundle": "^5.4|^6.0", - "symfony/semaphore": "^5.4|^6.0", - "symfony/serializer": "^6.3", - "symfony/stopwatch": "^5.4|^6.0", - "symfony/string": "^5.4|^6.0", - "symfony/translation": "^6.2.8", - "symfony/twig-bundle": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "symfony/validator": "^6.3", - "symfony/web-link": "^5.4|^6.0", - "symfony/workflow": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0", - "twig/twig": "^2.10|^3.0" + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/runtime": "^6.4.13|^7.1.6|^8.0", + "symfony/scheduler": "^6.4.4|^7.0.4|^8.0", + "symfony/security-bundle": "^6.4|^7.0|^8.0", + "symfony/semaphore": "^6.4|^7.0|^8.0", + "symfony/serializer": "^7.2.5|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/translation": "^7.3|^8.0", + "symfony/twig-bundle": "^6.4|^7.0|^8.0", + "symfony/type-info": "^7.1.8|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/web-link": "^6.4|^7.0|^8.0", + "symfony/webhook": "^7.2|^8.0", + "symfony/workflow": "^7.4|^8.0", + "symfony/yaml": "^7.3|^8.0", + "twig/twig": "^3.12" }, "type": "symfony-bundle", "autoload": { @@ -8643,7 +11976,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v6.3.2" + "source": "https://github.com/symfony/framework-bundle/tree/v7.4.3" }, "funding": [ { @@ -8654,37 +11987,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": "2023-07-26T17:39:03+00:00" + "time": "2025-12-29T09:31:36+00:00" }, { "name": "symfony/http-client", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00" + "reference": "d01dfac1e0dc99f18da48b18101c23ce57929616" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00", - "reference": "15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00", + "url": "https://api.github.com/repos/symfony/http-client/zipball/d01dfac1e0dc99f18da48b18101c23ce57929616", + "reference": "d01dfac1e0dc99f18da48b18101c23ce57929616", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "^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": "*", @@ -8693,18 +12033,20 @@ "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", - "guzzlehttp/promises": "^1.4", + "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", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/stopwatch": "^5.4|^6.0" + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -8735,7 +12077,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.3.2" + "source": "https://github.com/symfony/http-client/tree/v7.4.3" }, "funding": [ { @@ -8746,25 +12088,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": "2023-07-05T08:41:27+00:00" + "time": "2025-12-23T14:50:43+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.3.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "3b66325d0176b4ec826bffab57c9037d759c31fb" + "reference": "75d7043853a42837e68111812f4d964b01e5101c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/3b66325d0176b4ec826bffab57c9037d759c31fb", - "reference": "3b66325d0176b4ec826bffab57c9037d759c31fb", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", "shasum": "" }, "require": { @@ -8772,12 +12118,12 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -8813,7 +12159,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" }, "funding": [ { @@ -8829,40 +12175,41 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2025-04-29T11:18:49+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "43ed99d30f5f466ffa00bdac3f5f7aa9cd7617c3" + "reference": "a70c745d4cea48dbd609f4075e5f5cbce453bd52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/43ed99d30f5f466ffa00bdac3f5f7aa9cd7617c3", - "reference": "43ed99d30f5f466ffa00bdac3f5f7aa9cd7617c3", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a70c745d4cea48dbd609f4075e5f5cbce453bd52", + "reference": "a70c745d4cea48dbd609f4075e5f5cbce453bd52", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php83": "^1.27" + "symfony/polyfill-mbstring": "^1.1" }, "conflict": { - "symfony/cache": "<6.2" + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { - "doctrine/dbal": "^2.13.1|^3.0", + "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", - "symfony/mime": "^5.4|^6.0", - "symfony/rate-limiter": "^5.2|^6.0" + "symfony/cache": "^6.4.12|^7.1.5|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -8890,7 +12237,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.3.2" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.3" }, "funding": [ { @@ -8901,81 +12248,87 @@ "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": "2023-07-23T21:58:39+00:00" + "time": "2025-12-23T14:23:49+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.3.3", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "d3b567f0addf695e10b0c6d57564a9bea2e058ee" + "reference": "885211d4bed3f857b8c964011923528a55702aa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d3b567f0addf695e10b0c6d57564a9bea2e058ee", - "reference": "d3b567f0addf695e10b0c6d57564a9bea2e058ee", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/885211d4bed3f857b8c964011923528a55702aa5", + "reference": "885211d4bed3f857b8c964011923528a55702aa5", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^6.3", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/http-foundation": "^6.2.7", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^7.3|^8.0", + "symfony/http-foundation": "^7.4|^8.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/browser-kit": "<5.4", - "symfony/cache": "<5.4", - "symfony/config": "<6.1", - "symfony/console": "<5.4", - "symfony/dependency-injection": "<6.3", - "symfony/doctrine-bridge": "<5.4", - "symfony/form": "<5.4", - "symfony/http-client": "<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": "<6.4", + "symfony/flex": "<2.10", + "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/validator": "<5.4", - "symfony/var-dumper": "<6.3", - "twig/twig": "<2.13" + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "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", - "symfony/clock": "^6.2", - "symfony/config": "^6.1", - "symfony/console": "^5.4|^6.0", - "symfony/css-selector": "^5.4|^6.0", - "symfony/dependency-injection": "^6.3", - "symfony/dom-crawler": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", "symfony/http-client-contracts": "^2.5|^3", - "symfony/process": "^5.4|^6.0", - "symfony/property-access": "^5.4.5|^6.0.5", - "symfony/routing": "^5.4|^6.0", - "symfony/serializer": "^6.3", - "symfony/stopwatch": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^7.1|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/serializer": "^7.1|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^5.4|^6.0", - "symfony/validator": "^6.3", - "symfony/var-exporter": "^6.2", - "twig/twig": "^2.13|^3.0.4" + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12" }, "type": "library", "autoload": { @@ -9003,7 +12356,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.3.3" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.3" }, "funding": [ { @@ -9014,34 +12367,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": "2023-07-31T10:33:00+00:00" + "time": "2025-12-31T08:43:57+00:00" }, { "name": "symfony/intl", - "version": "v6.3.2", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "1f8cb145c869ed089a8531c51a6a4b31ed0b3c69" + "reference": "2fa074de6c7faa6b54f2891fc22708f42245ed5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/1f8cb145c869ed089a8531c51a6a4b31ed0b3c69", - "reference": "1f8cb145c869ed089a8531c51a6a4b31ed0b3c69", + "url": "https://api.github.com/repos/symfony/intl/zipball/2fa074de6c7faa6b54f2891fc22708f42245ed5c", + "reference": "2fa074de6c7faa6b54f2891fc22708f42245ed5c", "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", - "symfony/finder": "^5.4|^6.0", - "symfony/var-exporter": "^5.4|^6.0" + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -9049,7 +12409,8 @@ "Symfony\\Component\\Intl\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Tests/", + "/Resources/data/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -9085,7 +12446,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v6.3.2" + "source": "https://github.com/symfony/intl/tree/v7.4.0" }, "funding": [ { @@ -9096,48 +12457,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": "2023-07-20T07:43:09+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "symfony/mailer", - "version": "v6.3.0", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435" + "reference": "e472d35e230108231ccb7f51eb6b2100cac02ee4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/7b03d9be1dea29bfec0a6c7b603f5072a4c97435", - "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435", + "url": "https://api.github.com/repos/symfony/mailer/zipball/e472d35e230108231ccb7f51eb6b2100cac02ee4", + "reference": "e472d35e230108231ccb7f51eb6b2100cac02ee4", "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", - "symfony/mime": "^6.2", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/mime": "^7.2|^8.0", "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", - "symfony/http-client": "^5.4|^6.0", - "symfony/messenger": "^6.2", - "symfony/twig-bridge": "^6.2" + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/twig-bridge": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -9165,7 +12530,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.3.0" + "source": "https://github.com/symfony/mailer/tree/v7.4.3" }, "funding": [ { @@ -9176,29 +12541,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": "2023-05-29T12:49:39+00:00" + "time": "2025-12-16T08:02:06+00:00" }, { "name": "symfony/mime", - "version": "v6.3.3", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "9a0cbd52baa5ba5a5b1f0cacc59466f194730f98" + "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/9a0cbd52baa5ba5a5b1f0cacc59466f194730f98", - "reference": "9a0cbd52baa5ba5a5b1f0cacc59466f194730f98", + "url": "https://api.github.com/repos/symfony/mime/zipball/bdb02729471be5d047a3ac4a69068748f1a6be7a", + "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" @@ -9207,17 +12576,18 @@ "egulias/email-validator": "~3.0.0", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<5.4", - "symfony/serializer": "<6.2.13|>=6.3,<6.3.2" + "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", - "symfony/property-access": "^5.4|^6.0", - "symfony/property-info": "^5.4|^6.0", - "symfony/serializer": "~6.2.13|^6.3.2" + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4.3|^7.0.3|^8.0" }, "type": "library", "autoload": { @@ -9249,7 +12619,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.3.3" + "source": "https://github.com/symfony/mime/tree/v7.4.0" }, "funding": [ { @@ -9260,46 +12630,51 @@ "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": "2023-07-31T07:08:24+00:00" + "time": "2025-11-16T10:14:42+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v6.3.1", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "04b04b8e465e0fa84940e5609b6796a8b4e51bf1" + "reference": "189d16466ff83d9c51fad26382bf0beeb41bda21" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/04b04b8e465e0fa84940e5609b6796a8b4e51bf1", - "reference": "04b04b8e465e0fa84940e5609b6796a8b4e51bf1", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/189d16466ff83d9c51fad26382bf0beeb41bda21", + "reference": "189d16466ff83d9c51fad26382bf0beeb41bda21", "shasum": "" }, "require": { - "monolog/monolog": "^1.25.1|^2|^3", - "php": ">=8.1", - "symfony/http-kernel": "^5.4|^6.0", + "monolog/monolog": "^3", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0|^8.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/console": "<5.4", - "symfony/http-foundation": "<5.4", - "symfony/security-core": "<6.0" + "symfony/console": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/security-core": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/mailer": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/mime": "^5.4|^6.0", - "symfony/security-core": "^6.0", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/mailer": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/security-core": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "symfony-bridge", "autoload": { @@ -9327,7 +12702,7 @@ "description": "Provides integration for Monolog with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/monolog-bridge/tree/v6.3.1" + "source": "https://github.com/symfony/monolog-bridge/tree/v7.4.0" }, "funding": [ { @@ -9338,53 +12713,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": "2023-06-08T11:13:32+00:00" + "time": "2025-11-01T09:17:33+00:00" }, { "name": "symfony/monolog-bundle", - "version": "v3.8.0", + "version": "v3.11.1", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bundle.git", - "reference": "a41bbcdc1105603b6d73a7d9a43a3788f8e0fb7d" + "reference": "0e675a6e08f791ef960dc9c7e392787111a3f0c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/a41bbcdc1105603b6d73a7d9a43a3788f8e0fb7d", - "reference": "a41bbcdc1105603b6d73a7d9a43a3788f8e0fb7d", + "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/0e675a6e08f791ef960dc9c7e392787111a3f0c1", + "reference": "0e675a6e08f791ef960dc9c7e392787111a3f0c1", "shasum": "" }, "require": { - "monolog/monolog": "^1.22 || ^2.0 || ^3.0", - "php": ">=7.1.3", - "symfony/config": "~4.4 || ^5.0 || ^6.0", - "symfony/dependency-injection": "^4.4 || ^5.0 || ^6.0", - "symfony/http-kernel": "~4.4 || ^5.0 || ^6.0", - "symfony/monolog-bridge": "~4.4 || ^5.0 || ^6.0" + "composer-runtime-api": "^2.0", + "monolog/monolog": "^1.25.1 || ^2.0 || ^3.0", + "php": ">=8.1", + "symfony/config": "^6.4 || ^7.0", + "symfony/dependency-injection": "^6.4 || ^7.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/http-kernel": "^6.4 || ^7.0", + "symfony/monolog-bridge": "^6.4 || ^7.0", + "symfony/polyfill-php84": "^1.30" }, "require-dev": { - "symfony/console": "~4.4 || ^5.0 || ^6.0", - "symfony/phpunit-bridge": "^5.2 || ^6.0", - "symfony/yaml": "~4.4 || ^5.0 || ^6.0" + "symfony/console": "^6.4 || ^7.0", + "symfony/phpunit-bridge": "^7.3.3", + "symfony/yaml": "^6.4 || ^7.0" }, "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, "autoload": { "psr-4": { - "Symfony\\Bundle\\MonologBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Bundle\\MonologBundle\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -9408,7 +12782,7 @@ ], "support": { "issues": "https://github.com/symfony/monolog-bundle/issues", - "source": "https://github.com/symfony/monolog-bundle/tree/v3.8.0" + "source": "https://github.com/symfony/monolog-bundle/tree/v3.11.1" }, "funding": [ { @@ -9419,29 +12793,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": "2022-05-10T14:24:36+00:00" + "time": "2025-12-08T07:58:26+00:00" }, { "name": "symfony/options-resolver", - "version": "v6.3.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "a10f19f5198d589d5c33333cffe98dc9820332dd" + "reference": "b38026df55197f9e39a44f3215788edf83187b80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/a10f19f5198d589d5c33333cffe98dc9820332dd", - "reference": "a10f19f5198d589d5c33333cffe98dc9820332dd", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b38026df55197f9e39a44f3215788edf83187b80", + "reference": "b38026df55197f9e39a44f3215788edf83187b80", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -9475,7 +12853,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v6.3.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.4.0" }, "funding": [ { @@ -9486,36 +12864,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": "2023-05-12T14:21:09+00:00" + "time": "2025-11-12T15:39:26+00:00" }, { "name": "symfony/password-hasher", - "version": "v6.3.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/password-hasher.git", - "reference": "d23ad221989e6b8278d050cabfd7b569eee84590" + "reference": "aa075ce6f54fe931f03c1e382597912f4fd94e1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/password-hasher/zipball/d23ad221989e6b8278d050cabfd7b569eee84590", - "reference": "d23ad221989e6b8278d050cabfd7b569eee84590", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/aa075ce6f54fe931f03c1e382597912f4fd94e1e", + "reference": "aa075ce6f54fe931f03c1e382597912f4fd94e1e", "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", - "symfony/security-core": "^5.4|^6.0" + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/security-core": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -9547,7 +12929,7 @@ "password" ], "support": { - "source": "https://github.com/symfony/password-hasher/tree/v6.3.0" + "source": "https://github.com/symfony/password-hasher/tree/v7.4.0" }, "funding": [ { @@ -9558,29 +12940,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": "2023-02-14T09:04:20+00:00" + "time": "2025-08-13T16:46:49+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -9590,12 +12976,9 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9629,7 +13012,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -9640,41 +13023,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": "2022-11-03T14:55:06+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9710,7 +13094,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -9721,41 +13105,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": "2022-11-03T14:55:06+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.27.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "a3d9148e2c363588e05abbdd4ee4f971f0a5330c" + "reference": "bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/a3d9148e2c363588e05abbdd4ee4f971f0a5330c", - "reference": "a3d9148e2c363588e05abbdd4ee4f971f0a5330c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c", + "reference": "bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance and support of other locales than \"en\"" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9797,7 +13182,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.33.0" }, "funding": [ { @@ -9808,43 +13193,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": "2022-11-03T14:55:06+00:00" + "time": "2025-06-20T22:24:30+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.27.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da" + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-intl-normalizer": "^1.10", - "symfony/polyfill-php72": "^1.10" + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9884,7 +13269,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" }, "funding": [ { @@ -9895,41 +13280,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": "2022-11-03T14:55:06+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9968,7 +13354,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -9980,86 +13366,7 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -10067,193 +13374,30 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.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": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.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": "2022-11-03T14:55:06+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.27.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "508c652ba3ccf69f8c97f251534f229791b52a57" + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/508c652ba3ccf69f8c97f251534f229791b52a57", - "reference": "508c652ba3ccf69f8c97f251534f229791b52a57", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-php80": "^1.14" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -10262,7 +13406,10 @@ ], "psr-4": { "Symfony\\Polyfill\\Php83\\": "" - } + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -10287,7 +13434,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" }, "funding": [ { @@ -10298,29 +13445,193 @@ "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": "2022-11-03T14:55:06+00:00" + "time": "2025-07-08T02:45:35+00:00" }, { - "name": "symfony/polyfill-uuid", - "version": "v1.27.0", + "name": "symfony/polyfill-php84", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/f3cf1a645c2734236ed1e2e671e273eeb3586166", - "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-23T16:12:55+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "shasum": "" + }, + "require": { + "php": ">=7.2" }, "provide": { "ext-uuid": "*" @@ -10330,12 +13641,9 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -10369,7 +13677,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" }, "funding": [ { @@ -10380,29 +13688,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": "2022-11-03T14:55:06+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/process", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d" + "reference": "2f8e1a6cdf590ca63715da4d3a7a3327404a523f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d", - "reference": "c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d", + "url": "https://api.github.com/repos/symfony/process/zipball/2f8e1a6cdf590ca63715da4d3a7a3327404a523f", + "reference": "2f8e1a6cdf590ca63715da4d3a7a3327404a523f", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", "autoload": { @@ -10430,7 +13742,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.3.2" + "source": "https://github.com/symfony/process/tree/v7.4.3" }, "funding": [ { @@ -10441,34 +13753,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": "2023-07-12T16:00:22+00:00" + "time": "2025-12-19T10:00:43+00:00" }, { "name": "symfony/property-access", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "2dc4f9da444b8f8ff592e95d570caad67924f1d0" + "reference": "30aff8455647be949fc2e8fcef2847d5a6743c98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/2dc4f9da444b8f8ff592e95d570caad67924f1d0", - "reference": "2dc4f9da444b8f8ff592e95d570caad67924f1d0", + "url": "https://api.github.com/repos/symfony/property-access/zipball/30aff8455647be949fc2e8fcef2847d5a6743c98", + "reference": "30aff8455647be949fc2e8fcef2847d5a6743c98", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/property-info": "^5.4|^6.0" + "php": ">=8.2", + "symfony/property-info": "^6.4.31|~7.3.9|^7.4.2|^8.0.3" }, "require-dev": { - "symfony/cache": "^5.4|^6.0" + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4.1|^7.0.1|^8.0" }, "type": "library", "autoload": { @@ -10507,7 +13823,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v6.3.2" + "source": "https://github.com/symfony/property-access/tree/v7.4.3" }, "funding": [ { @@ -10518,43 +13834,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": "2023-07-13T15:26:11+00:00" + "time": "2025-12-18T10:35:58+00:00" }, { "name": "symfony/property-info", - "version": "v6.3.0", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "7f3a03716112269741fe2a809f8f791a371d1fcd" + "reference": "ea62b28cd68fb36e252abd77de61e505a0f2a7b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/7f3a03716112269741fe2a809f8f791a371d1fcd", - "reference": "7f3a03716112269741fe2a809f8f791a371d1fcd", + "url": "https://api.github.com/repos/symfony/property-info/zipball/ea62b28cd68fb36e252abd77de61e505a0f2a7b1", + "reference": "ea62b28cd68fb36e252abd77de61e505a0f2a7b1", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/string": "^5.4|^6.0" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/type-info": "~7.3.8|^7.4.1|^8.0.1" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2", "phpdocumentor/type-resolver": "<1.5.1", - "symfony/dependency-injection": "<5.4" + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/serializer": "<6.4" }, "require-dev": { - "doctrine/annotations": "^1.10.4|^2", "phpdocumentor/reflection-docblock": "^5.2", - "phpstan/phpdoc-parser": "^1.0", - "symfony/cache": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0" + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -10590,7 +13913,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v6.3.0" + "source": "https://github.com/symfony/property-info/tree/v7.4.3" }, "funding": [ { @@ -10602,70 +13925,7 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-05-19T08:06:44+00:00" - }, - { - "name": "symfony/proxy-manager-bridge", - "version": "v6.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/proxy-manager-bridge.git", - "reference": "7ba2ac62c88d7c3460d41f04ceba5fc3b9071a39" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/7ba2ac62c88d7c3460d41f04ceba5fc3b9071a39", - "reference": "7ba2ac62c88d7c3460d41f04ceba5fc3b9071a39", - "shasum": "" - }, - "require": { - "friendsofphp/proxy-manager-lts": "^1.0.2", - "php": ">=8.1", - "symfony/dependency-injection": "^6.3", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "require-dev": { - "symfony/config": "^6.1" - }, - "type": "symfony-bridge", - "autoload": { - "psr-4": { - "Symfony\\Bridge\\ProxyManager\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "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": "Provides integration for ProxyManager with various Symfony components", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/proxy-manager-bridge/tree/v6.3.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -10673,47 +13933,43 @@ "type": "tidelift" } ], - "time": "2023-05-26T07:49:33+00:00" + "time": "2025-12-18T08:28:41+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v2.3.1", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e" + "reference": "0101ff8bd0506703b045b1670960302d302a726c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/581ca6067eb62640de5ff08ee1ba6850a0ee472e", - "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/0101ff8bd0506703b045b1670960302d302a726c", + "reference": "0101ff8bd0506703b045b1670960302d302a726c", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/http-message": "^1.0 || ^2.0", - "symfony/deprecation-contracts": "^2.5 || ^3.0", - "symfony/http-foundation": "^5.4 || ^6.0" + "php": ">=8.2", + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-kernel": "<6.4" }, "require-dev": { "nyholm/psr7": "^1.1", - "psr/log": "^1.1 || ^2 || ^3", - "symfony/browser-kit": "^5.4 || ^6.0", - "symfony/config": "^5.4 || ^6.0", - "symfony/event-dispatcher": "^5.4 || ^6.0", - "symfony/framework-bundle": "^5.4 || ^6.0", - "symfony/http-kernel": "^5.4 || ^6.0", - "symfony/phpunit-bridge": "^6.2" - }, - "suggest": { - "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", + "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", + "symfony/runtime": "^6.4.13|^7.1.6|^8.0" }, "type": "symfony-bridge", - "extra": { - "branch-alias": { - "dev-main": "2.3-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" @@ -10733,11 +13989,11 @@ }, { "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "homepage": "https://symfony.com/contributors" } ], "description": "PSR HTTP message bridge", - "homepage": "http://symfony.com", + "homepage": "https://symfony.com", "keywords": [ "http", "http-message", @@ -10745,8 +14001,7 @@ "psr-7" ], "support": { - "issues": "https://github.com/symfony/psr-http-message-bridge/issues", - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.3.1" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.4.0" }, "funding": [ { @@ -10757,34 +14012,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": "2023-07-26T11:53:26+00:00" + "time": "2025-11-13T08:38:49+00:00" }, { "name": "symfony/rate-limiter", - "version": "v6.3.2", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/rate-limiter.git", - "reference": "5223387e4017f26c8c61c46d8e39c11fe4d504b7" + "reference": "5c6df5bc10308505bb0fa8d1388bc6bd8a628ba8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/5223387e4017f26c8c61c46d8e39c11fe4d504b7", - "reference": "5223387e4017f26c8c61c46d8e39c11fe4d504b7", + "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/5c6df5bc10308505bb0fa8d1388bc6bd8a628ba8", + "reference": "5c6df5bc10308505bb0fa8d1388bc6bd8a628ba8", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/options-resolver": "^5.4|^6.0" + "php": ">=8.2", + "symfony/options-resolver": "^7.3|^8.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/lock": "^5.4|^6.0" + "symfony/lock": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -10816,7 +14075,7 @@ "rate-limiter" ], "support": { - "source": "https://github.com/symfony/rate-limiter/tree/v6.3.2" + "source": "https://github.com/symfony/rate-limiter/tree/v7.4.0" }, "funding": [ { @@ -10827,45 +14086,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": "2023-07-13T14:29:38+00:00" + "time": "2025-08-04T07:05:15+00:00" }, { "name": "symfony/routing", - "version": "v6.3.3", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "e7243039ab663822ff134fbc46099b5fdfa16f6a" + "reference": "5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/e7243039ab663822ff134fbc46099b5fdfa16f6a", - "reference": "e7243039ab663822ff134fbc46099b5fdfa16f6a", + "url": "https://api.github.com/repos/symfony/routing/zipball/5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090", + "reference": "5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090", "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", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0" + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -10899,7 +14160,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.3.3" + "source": "https://github.com/symfony/routing/tree/v7.4.3" }, "funding": [ { @@ -10910,40 +14171,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": "2023-07-31T07:08:24+00:00" + "time": "2025-12-19T10:00:43+00:00" }, { "name": "symfony/runtime", - "version": "v6.3.2", + "version": "v7.4.1", "source": { "type": "git", "url": "https://github.com/symfony/runtime.git", - "reference": "d5c09493647a0c1a16e6c8da308098e840d1164f" + "reference": "876f902a6cb6b26c003de244188c06b2ba1c172f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/d5c09493647a0c1a16e6c8da308098e840d1164f", - "reference": "d5c09493647a0c1a16e6c8da308098e840d1164f", + "url": "https://api.github.com/repos/symfony/runtime/zipball/876f902a6cb6b26c003de244188c06b2ba1c172f", + "reference": "876f902a6cb6b26c003de244188c06b2ba1c172f", "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", - "symfony/dotenv": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0" + "composer/composer": "^2.6", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dotenv": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0" }, "type": "composer-plugin", "extra": { @@ -10978,7 +14243,7 @@ "runtime" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v6.3.2" + "source": "https://github.com/symfony/runtime/tree/v7.4.1" }, "funding": [ { @@ -10989,78 +14254,80 @@ "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": "2023-07-16T17:05:46+00:00" + "time": "2025-12-05T14:04:53+00:00" }, { "name": "symfony/security-bundle", - "version": "v6.3.3", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "b33382ca3034ee691dd0d882f214ae9e037f4427" + "reference": "48a64e746857464a5e8fd7bab84b31c9ba967eb9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/b33382ca3034ee691dd0d882f214ae9e037f4427", - "reference": "b33382ca3034ee691dd0d882f214ae9e037f4427", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/48a64e746857464a5e8fd7bab84b31c9ba967eb9", + "reference": "48a64e746857464a5e8fd7bab84b31c9ba967eb9", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=8.1", - "symfony/clock": "^6.3", - "symfony/config": "^6.1", - "symfony/dependency-injection": "^6.2", + "php": ">=8.2", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^6.4.11|^7.1.4|^8.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/http-foundation": "^6.2", - "symfony/http-kernel": "^6.2", - "symfony/password-hasher": "^5.4|^6.0", - "symfony/security-core": "^6.2", - "symfony/security-csrf": "^5.4|^6.0", - "symfony/security-http": "^6.3" + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", + "symfony/password-hasher": "^6.4|^7.0|^8.0", + "symfony/security-core": "^7.4|^8.0", + "symfony/security-csrf": "^6.4|^7.0|^8.0", + "symfony/security-http": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/browser-kit": "<5.4", - "symfony/console": "<5.4", - "symfony/framework-bundle": "<6.3", - "symfony/http-client": "<5.4", - "symfony/ldap": "<5.4", - "symfony/twig-bundle": "<5.4" + "symfony/browser-kit": "<6.4", + "symfony/console": "<6.4", + "symfony/framework-bundle": "<6.4", + "symfony/http-client": "<6.4", + "symfony/ldap": "<6.4", + "symfony/serializer": "<6.4", + "symfony/twig-bundle": "<6.4", + "symfony/validator": "<6.4" }, "require-dev": { - "doctrine/annotations": "^1.10.4|^2", - "symfony/asset": "^5.4|^6.0", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/console": "^5.4|^6.0", - "symfony/css-selector": "^5.4|^6.0", - "symfony/dom-crawler": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/form": "^5.4|^6.0", - "symfony/framework-bundle": "^6.3", - "symfony/http-client": "^5.4|^6.0", - "symfony/ldap": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/rate-limiter": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", - "symfony/twig-bridge": "^5.4|^6.0", - "symfony/twig-bundle": "^5.4|^6.0", - "symfony/validator": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.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/asset": "^6.4|^7.0|^8.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/form": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/ldap": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/runtime": "^6.4.13|^7.1.6|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/twig-bridge": "^6.4|^7.0|^8.0", + "symfony/twig-bundle": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0", + "twig/twig": "^3.15", + "web-token/jwt-library": "^3.3.2|^4.0" }, "type": "symfony-bundle", "autoload": { @@ -11088,7 +14355,7 @@ "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-bundle/tree/v6.3.3" + "source": "https://github.com/symfony/security-bundle/tree/v7.4.0" }, "funding": [ { @@ -11099,53 +14366,59 @@ "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": "2023-07-31T07:08:24+00:00" + "time": "2025-11-14T09:57:20+00:00" }, { "name": "symfony/security-core", - "version": "v6.3.3", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "b86ce012cc9a62a15ed43af5037eebc3e6de4d7f" + "reference": "be0b8585f2d69b48a9b1a6372aa48d23c7e7eeb4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/b86ce012cc9a62a15ed43af5037eebc3e6de4d7f", - "reference": "b86ce012cc9a62a15ed43af5037eebc3e6de4d7f", + "url": "https://api.github.com/repos/symfony/security-core/zipball/be0b8585f2d69b48a9b1a6372aa48d23c7e7eeb4", + "reference": "be0b8585f2d69b48a9b1a6372aa48d23c7e7eeb4", "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", + "symfony/password-hasher": "^6.4|^7.0|^8.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/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", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/ldap": "^5.4|^6.0", - "symfony/string": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", - "symfony/validator": "^5.4|^6.0" + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/ldap": "^6.4|^7.0|^8.0", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4.3|^7.0.3|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -11173,7 +14446,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v6.3.3" + "source": "https://github.com/symfony/security-core/tree/v7.4.3" }, "funding": [ { @@ -11184,36 +14457,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": "2023-07-31T07:08:24+00:00" + "time": "2025-12-19T23:18:26+00:00" }, { "name": "symfony/security-csrf", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "63d7b098c448cbddb46ea5eda33b68c1ece6eb5b" + "reference": "d526fa61963d926e91c9fb22edf829d9f8793dfe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/63d7b098c448cbddb46ea5eda33b68c1ece6eb5b", - "reference": "63d7b098c448cbddb46ea5eda33b68c1ece6eb5b", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/d526fa61963d926e91c9fb22edf829d9f8793dfe", + "reference": "d526fa61963d926e91c9fb22edf829d9f8793dfe", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/security-core": "^5.4|^6.0" + "php": ">=8.2", + "symfony/security-core": "^6.4|^7.0|^8.0" }, "conflict": { - "symfony/http-foundation": "<5.4" + "symfony/http-foundation": "<6.4" }, "require-dev": { - "symfony/http-foundation": "^5.4|^6.0" + "psr/log": "^1|^2|^3", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -11241,7 +14520,7 @@ "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-csrf/tree/v6.3.2" + "source": "https://github.com/symfony/security-csrf/tree/v7.4.3" }, "funding": [ { @@ -11252,55 +14531,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": "2023-07-05T08:41:27+00:00" + "time": "2025-12-23T15:24:11+00:00" }, { "name": "symfony/security-http", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "04d6b868786a56c1fadc52b003fe5a4f9ab3f3d0" + "reference": "72f3b3fa9f322c9579d5246895a09f945cc33e36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/04d6b868786a56c1fadc52b003fe5a4f9ab3f3d0", - "reference": "04d6b868786a56c1fadc52b003fe5a4f9ab3f3d0", + "url": "https://api.github.com/repos/symfony/security-http/zipball/72f3b3fa9f322c9579d5246895a09f945cc33e36", + "reference": "72f3b3fa9f322c9579d5246895a09f945cc33e36", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^6.3", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", "symfony/polyfill-mbstring": "~1.0", - "symfony/property-access": "^5.4|^6.0", - "symfony/security-core": "^6.3" + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/security-core": "^7.3|^8.0", + "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/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", - "symfony/clock": "^6.3", - "symfony/expression-language": "^5.4|^6.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", "symfony/http-client-contracts": "^3.0", - "symfony/rate-limiter": "^5.4|^6.0", - "symfony/routing": "^5.4|^6.0", - "symfony/security-csrf": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", - "web-token/jwt-checker": "^3.1", - "web-token/jwt-signature-algorithm-ecdsa": "^3.1" + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/security-csrf": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", + "web-token/jwt-library": "^3.3.2|^4.0" }, "type": "library", "autoload": { @@ -11328,7 +14612,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v6.3.2" + "source": "https://github.com/symfony/security-http/tree/v7.4.3" }, "funding": [ { @@ -11339,62 +14623,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": "2023-07-13T14:29:38+00:00" + "time": "2025-12-19T23:18:26+00:00" }, { "name": "symfony/serializer", - "version": "v6.3.3", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "33deb86d212893042d7758d452aa39d19ca0efe3" + "reference": "af01e99d6fc63549063fb9e849ce1240cfef5c4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/33deb86d212893042d7758d452aa39d19ca0efe3", - "reference": "33deb86d212893042d7758d452aa39d19ca0efe3", + "url": "https://api.github.com/repos/symfony/serializer/zipball/af01e99d6fc63549063fb9e849ce1240cfef5c4a", + "reference": "af01e99d6fc63549063fb9e849ce1240cfef5c4a", "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/yaml": "<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": "<6.4" }, "require-dev": { - "doctrine/annotations": "^1.12|^2", "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", - "symfony/cache": "^5.4|^6.0", - "symfony/config": "^5.4|^6.0", - "symfony/console": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/error-handler": "^5.4|^6.0", - "symfony/filesystem": "^5.4|^6.0", - "symfony/form": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/mime": "^5.4|^6.0", - "symfony/property-access": "^5.4|^6.0", - "symfony/property-info": "^5.4.24|^6.2.11", - "symfony/uid": "^5.4|^6.0", - "symfony/validator": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0", - "symfony/var-exporter": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0" + "phpstan/phpdoc-parser": "^1.0|^2.0", + "seld/jsonlint": "^1.10", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^7.2|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/form": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/type-info": "^7.1.8|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -11422,7 +14715,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v6.3.3" + "source": "https://github.com/symfony/serializer/tree/v7.4.3" }, "funding": [ { @@ -11433,42 +14726,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": "2023-07-31T07:08:24+00:00" + "time": "2025-12-23T14:50:43+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.3.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", - "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { "php": ">=8.1", - "psr/container": "^2.0" + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -11504,7 +14802,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -11515,40 +14813,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": "2023-05-23T14:45:45+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/stimulus-bundle", - "version": "v2.10.0", + "version": "v2.31.0", "source": { "type": "git", "url": "https://github.com/symfony/stimulus-bundle.git", - "reference": "257ef052bebe491d7c29e9a4a8009edb82269e15" + "reference": "c5ea8ee2ccd45447b7f4b6b82f704ee5e76127f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/257ef052bebe491d7c29e9a4a8009edb82269e15", - "reference": "257ef052bebe491d7c29e9a4a8009edb82269e15", + "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/c5ea8ee2ccd45447b7f4b6b82f704ee5e76127f0", + "reference": "c5ea8ee2ccd45447b7f4b6b82f704ee5e76127f0", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "twig/twig": "^2.15.3|^3.4.3" + "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|^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.x-dev", - "symfony/framework-bundle": "^5.4|^6.0", - "symfony/phpunit-bridge": "^5.4|^6.0", - "symfony/twig-bundle": "^5.4|^6.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", @@ -11572,7 +14875,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/stimulus-bundle/tree/v2.10.0" + "source": "https://github.com/symfony/stimulus-bundle/tree/v2.31.0" }, "funding": [ { @@ -11583,29 +14886,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": "2023-06-29T19:46:37+00:00" + "time": "2025-09-24T13:27:42+00:00" }, { "name": "symfony/stopwatch", - "version": "v6.3.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2" + "reference": "8a24af0a2e8a872fb745047180649b8418303084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", - "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/8a24af0a2e8a872fb745047180649b8418303084", + "reference": "8a24af0a2e8a872fb745047180649b8418303084", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/service-contracts": "^2.5|^3" }, "type": "library", @@ -11634,7 +14941,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v6.3.0" + "source": "https://github.com/symfony/stopwatch/tree/v7.4.0" }, "funding": [ { @@ -11645,31 +14952,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": "2023-02-16T10:14:28+00:00" + "time": "2025-08-04T07:05:15+00:00" }, { "name": "symfony/string", - "version": "v6.3.2", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "53d1a83225002635bca3482fcbf963001313fb68" + "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/53d1a83225002635bca3482fcbf963001313fb68", - "reference": "53d1a83225002635bca3482fcbf963001313fb68", + "url": "https://api.github.com/repos/symfony/string/zipball/d50e862cb0a0e0886f73ca1f31b865efbb795003", + "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-grapheme": "~1.33", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0" }, @@ -11677,11 +14989,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/intl": "^6.2", + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0" + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -11720,7 +15032,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.3.2" + "source": "https://github.com/symfony/string/tree/v7.4.0" }, "funding": [ { @@ -11731,60 +15043,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": "2023-07-05T08:41:27+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "symfony/translation", - "version": "v6.3.3", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd" + "reference": "7ef27c65d78886f7599fdd5c93d12c9243ecf44d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd", - "reference": "3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd", + "url": "https://api.github.com/repos/symfony/translation/zipball/7ef27c65d78886f7599fdd5c93d12c9243ecf44d", + "reference": "7ef27c65d78886f7599fdd5c93d12c9243ecf44d", "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" + "symfony/translation-contracts": "^2.5.3|^3.3" }, "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.13", + "nikic/php-parser": "^5.0", "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/console": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/intl": "^5.4|^6.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^5.4|^6.0", + "symfony/routing": "^6.4|^7.0|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0" + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -11815,7 +15132,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.3.3" + "source": "https://github.com/symfony/translation/tree/v7.4.3" }, "funding": [ { @@ -11826,25 +15143,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": "2023-07-31T07:08:24+00:00" + "time": "2025-12-29T09:31:36+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.3.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86" + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/02c24deb352fb0d79db5486c0c79905a85e37e86", - "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", "shasum": "" }, "require": { @@ -11852,12 +15173,12 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -11893,7 +15214,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" }, "funding": [ { @@ -11904,76 +15225,83 @@ "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": "2023-05-30T17:17:10+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/twig-bridge", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "6f8435db76a2d79917489a19a82679276c1b4e32" + "reference": "43c922fce020060c65b0fd54bfd8def3b38949b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/6f8435db76a2d79917489a19a82679276c1b4e32", - "reference": "6f8435db76a2d79917489a19a82679276c1b4e32", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/43c922fce020060c65b0fd54bfd8def3b38949b6", + "reference": "43c922fce020060c65b0fd54bfd8def3b38949b6", "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/http-kernel": "<6.2", - "symfony/mime": "<6.2", - "symfony/translation": "<5.4", - "symfony/workflow": "<5.4" + "symfony/console": "<6.4", + "symfony/form": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/mime": "<6.4", + "symfony/serializer": "<6.4", + "symfony/translation": "<6.4", + "symfony/workflow": "<6.4" }, "require-dev": { - "doctrine/annotations": "^1.12|^2", "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", - "symfony/asset-mapper": "^6.3", - "symfony/console": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", - "symfony/form": "^6.3", - "symfony/html-sanitizer": "^6.1", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^6.2", - "symfony/intl": "^5.4|^6.0", - "symfony/mime": "^6.2", + "symfony/asset": "^6.4|^7.0|^8.0", + "symfony/asset-mapper": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/emoji": "^7.1|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/form": "^6.4.30|~7.3.8|^7.4.1|^8.0.1", + "symfony/html-sanitizer": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^7.3|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^5.4|^6.0", - "symfony/routing": "^5.4|^6.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^5.4|^6.0", - "symfony/security-csrf": "^5.4|^6.0", - "symfony/security-http": "^5.4|^6.0", - "symfony/serializer": "^6.2", - "symfony/stopwatch": "^5.4|^6.0", - "symfony/translation": "^6.1", - "symfony/web-link": "^5.4|^6.0", - "symfony/workflow": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0", - "twig/cssinliner-extra": "^2.12|^3", - "twig/inky-extra": "^2.12|^3", - "twig/markdown-extra": "^2.12|^3" + "symfony/security-core": "^6.4|^7.0|^8.0", + "symfony/security-csrf": "^6.4|^7.0|^8.0", + "symfony/security-http": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4.3|^7.0.3|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/web-link": "^6.4|^7.0|^8.0", + "symfony/workflow": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0", + "twig/cssinliner-extra": "^3", + "twig/inky-extra": "^3", + "twig/markdown-extra": "^3" }, "type": "symfony-bridge", "autoload": { @@ -12001,7 +15329,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v6.3.2" + "source": "https://github.com/symfony/twig-bridge/tree/v7.4.3" }, "funding": [ { @@ -12012,53 +15340,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": "2023-07-20T16:42:33+00:00" + "time": "2025-12-16T08:02:06+00:00" }, { "name": "symfony/twig-bundle", - "version": "v6.3.0", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "d0cd4d1675c0582d27c2e8bb0dc27c0303d8e3ea" + "reference": "9e1f5fd2668ed26c60d17d63f15fe270ed8da5e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/d0cd4d1675c0582d27c2e8bb0dc27c0303d8e3ea", - "reference": "d0cd4d1675c0582d27c2e8bb0dc27c0303d8e3ea", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/9e1f5fd2668ed26c60d17d63f15fe270ed8da5e6", + "reference": "9e1f5fd2668ed26c60d17d63f15fe270ed8da5e6", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", - "php": ">=8.1", - "symfony/config": "^6.1", - "symfony/dependency-injection": "^6.1", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^6.2", - "symfony/twig-bridge": "^6.3", - "twig/twig": "^2.13|^3.0.4" + "php": ">=8.2", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", + "symfony/twig-bridge": "^7.3|^8.0", + "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": { - "doctrine/annotations": "^1.10.4|^2", - "symfony/asset": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", - "symfony/form": "^5.4|^6.0", - "symfony/framework-bundle": "^5.4|^6.0", - "symfony/routing": "^5.4|^6.0", - "symfony/stopwatch": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", - "symfony/web-link": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0" + "symfony/asset": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/form": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/runtime": "^6.4.13|^7.1.6", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/web-link": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "symfony-bundle", "autoload": { @@ -12086,7 +15419,7 @@ "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v6.3.0" + "source": "https://github.com/symfony/twig-bundle/tree/v7.4.3" }, "funding": [ { @@ -12097,33 +15430,120 @@ "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": "2023-05-06T09:53:41+00:00" + "time": "2025-12-19T10:00:43+00:00" }, { - "name": "symfony/uid", - "version": "v6.3.0", + "name": "symfony/type-info", + "version": "v7.4.1", "source": { "type": "git", - "url": "https://github.com/symfony/uid.git", - "reference": "01b0f20b1351d997711c56f1638f7a8c3061e384" + "url": "https://github.com/symfony/type-info.git", + "reference": "ac5ab66b21c758df71b7210cf1033d1ac807f202" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/01b0f20b1351d997711c56f1638f7a8c3061e384", - "reference": "01b0f20b1351d997711c56f1638f7a8c3061e384", + "url": "https://api.github.com/repos/symfony/type-info/zipball/ac5ab66b21c758df71b7210cf1033d1ac807f202", + "reference": "ac5ab66b21c758df71b7210cf1033d1ac807f202", "shasum": "" }, "require": { - "php": ">=8.1", + "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.4.1" + }, + "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-12-05T14:04:53+00:00" + }, + { + "name": "symfony/uid", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "2498e9f81b7baa206f44de583f2f48350b90142c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/2498e9f81b7baa206f44de583f2f48350b90142c", + "reference": "2498e9f81b7baa206f44de583f2f48350b90142c", + "shasum": "" + }, + "require": { + "php": ">=8.2", "symfony/polyfill-uuid": "^1.15" }, "require-dev": { - "symfony/console": "^5.4|^6.0" + "symfony/console": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -12160,7 +15580,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.3.0" + "source": "https://github.com/symfony/uid/tree/v7.4.0" }, "funding": [ { @@ -12171,44 +15591,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": "2023-04-08T07:25:02+00:00" + "time": "2025-09-25T11:02:55+00:00" }, { "name": "symfony/ux-translator", - "version": "v2.10.0", + "version": "v2.31.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-translator.git", - "reference": "9f49e121558d7c1fab134031b878dfc884775da0" + "reference": "b4b323fdc846d2d67feb7f8ca5ef5a05238f6639" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-translator/zipball/9f49e121558d7c1fab134031b878dfc884775da0", - "reference": "9f49e121558d7c1fab134031b878dfc884775da0", + "url": "https://api.github.com/repos/symfony/ux-translator/zipball/b4b323fdc846d2d67feb7f8ca5ef5a05238f6639", + "reference": "b4b323fdc846d2d67feb7f8ca5ef5a05238f6639", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/console": "^5.4|^6.0", - "symfony/filesystem": "^5.4|^6.0", - "symfony/string": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.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", - "symfony/phpunit-bridge": "^5.2|^6.0", - "symfony/var-dumper": "^5.4|^6.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": { "thanks": { - "name": "symfony/ux", - "url": "https://github.com/symfony/ux" + "url": "https://github.com/symfony/ux", + "name": "symfony/ux" } }, "autoload": { @@ -12236,7 +15661,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/ux-translator/tree/v2.10.0" + "source": "https://github.com/symfony/ux-translator/tree/v2.31.0" }, "funding": [ { @@ -12247,25 +15672,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": "2023-06-19T18:55:23+00:00" + "time": "2025-10-16T07:24:06+00:00" }, { "name": "symfony/ux-turbo", - "version": "v2.10.0", + "version": "v2.31.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-turbo.git", - "reference": "9762c373ed54c3642a4ae64b9254bc1c454f7da0" + "reference": "06d5e4cf4573efe4faf648f3810a28c63684c706" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/9762c373ed54c3642a4ae64b9254bc1c454f7da0", - "reference": "9762c373ed54c3642a4ae64b9254bc1c454f7da0", + "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/06d5e4cf4573efe4faf648f3810a28c63684c706", + "reference": "06d5e4cf4573efe4faf648f3810a28c63684c706", "shasum": "" }, "require": { @@ -12276,30 +15705,33 @@ "symfony/flex": "<1.13" }, "require-dev": { - "doctrine/annotations": "^1.12", + "dbrekelmans/bdi": "dev-main", "doctrine/doctrine-bundle": "^2.4.3", - "doctrine/orm": "^2.8 | 3.0", - "phpstan/phpstan": "^1.10", - "symfony/debug-bundle": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/form": "^5.4|^6.0", - "symfony/framework-bundle": "^5.4|^6.0", - "symfony/mercure-bundle": "^0.3.4", - "symfony/messenger": "^5.4|^6.0", - "symfony/panther": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4|^6.0", - "symfony/property-access": "^5.4|^6.0", - "symfony/security-core": "^5.4|^6.0", - "symfony/stopwatch": "^5.4|^6.0", - "symfony/twig-bundle": "^5.4|^6.0", - "symfony/web-profiler-bundle": "^5.4|^6.0", - "symfony/webpack-encore-bundle": "^1.11" + "doctrine/orm": "^2.8|^3.0", + "php-webdriver/webdriver": "^1.15", + "phpstan/phpstan": "^2.1.17", + "symfony/asset-mapper": "^6.4|^7.0|^8.0", + "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|^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|^8.0" }, "type": "symfony-bundle", "extra": { "thanks": { - "name": "symfony/ux", - "url": "https://github.com/symfony/ux" + "url": "https://github.com/symfony/ux", + "name": "symfony/ux" } }, "autoload": { @@ -12332,7 +15764,7 @@ "turbo-stream" ], "support": { - "source": "https://github.com/symfony/ux-turbo/tree/v2.10.0" + "source": "https://github.com/symfony/ux-turbo/tree/v2.31.0" }, "funding": [ { @@ -12343,29 +15775,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": "2023-06-19T13:59:38+00:00" + "time": "2025-10-16T07:24:06+00:00" }, { "name": "symfony/validator", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "b0c4ecf17d39eee1edfecc92299a03b9f5d5220b" + "reference": "9670bedf4c454b21d1e04606b6c227990da8bebe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/b0c4ecf17d39eee1edfecc92299a03b9f5d5220b", - "reference": "b0c4ecf17d39eee1edfecc92299a03b9f5d5220b", + "url": "https://api.github.com/repos/symfony/validator/zipball/9670bedf4c454b21d1e04606b6c227990da8bebe", + "reference": "9670bedf4c454b21d1e04606b6c227990da8bebe", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", @@ -12373,34 +15809,37 @@ "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", - "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/var-exporter": "<6.4.25|>=7.0,<7.3.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", - "symfony/config": "^5.4|^6.0", - "symfony/console": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/intl": "^5.4|^6.0", - "symfony/mime": "^5.4|^6.0", - "symfony/property-access": "^5.4|^6.0", - "symfony/property-info": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0" + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4.3|^7.0.3|^8.0", + "symfony/type-info": "^7.1.8", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -12408,7 +15847,8 @@ "Symfony\\Component\\Validator\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Tests/", + "/Resources/bin/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -12428,7 +15868,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v6.3.2" + "source": "https://github.com/symfony/validator/tree/v7.4.3" }, "funding": [ { @@ -12439,42 +15879,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": "2023-07-26T17:39:03+00:00" + "time": "2025-12-27T17:05:22+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.3.3", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "77fb4f2927f6991a9843633925d111147449ee7a" + "reference": "7e99bebcb3f90d8721890f2963463280848cba92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/77fb4f2927f6991a9843633925d111147449ee7a", - "reference": "77fb4f2927f6991a9843633925d111147449ee7a", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7e99bebcb3f90d8721890f2963463280848cba92", + "reference": "7e99bebcb3f90d8721890f2963463280848cba92", "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", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12" }, "bin": [ "Resources/bin/var-dump-server" @@ -12512,7 +15955,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.3.3" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.3" }, "funding": [ { @@ -12523,32 +15966,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": "2023-07-31T07:08:24+00:00" + "time": "2025-12-18T07:04:31+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.3.2", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "3400949782c0cb5b3e73aa64cfd71dde000beccc" + "reference": "03a60f169c79a28513a78c967316fbc8bf17816f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/3400949782c0cb5b3e73aa64cfd71dde000beccc", - "reference": "3400949782c0cb5b3e73aa64cfd71dde000beccc", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/03a60f169c79a28513a78c967316fbc8bf17816f", + "reference": "03a60f169c79a28513a78c967316fbc8bf17816f", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "symfony/var-dumper": "^5.4|^6.0" + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -12586,7 +16036,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.3.2" + "source": "https://github.com/symfony/var-exporter/tree/v7.4.0" }, "funding": [ { @@ -12597,39 +16047,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": "2023-07-26T17:39:03+00:00" + "time": "2025-09-11T10:15:23+00:00" }, { "name": "symfony/web-link", - "version": "v6.3.0", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/web-link.git", - "reference": "0989ca617d0703cdca501a245f10e194ff22315b" + "reference": "c62edd6b52e31cf2f6f38fd3386725f364f19942" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-link/zipball/0989ca617d0703cdca501a245f10e194ff22315b", - "reference": "0989ca617d0703cdca501a245f10e194ff22315b", + "url": "https://api.github.com/repos/symfony/web-link/zipball/c62edd6b52e31cf2f6f38fd3386725f364f19942", + "reference": "c62edd6b52e31cf2f6f38fd3386725f364f19942", "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" + "symfony/http-kernel": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -12669,7 +16123,7 @@ "push" ], "support": { - "source": "https://github.com/symfony/web-link/tree/v6.3.0" + "source": "https://github.com/symfony/web-link/tree/v7.4.0" }, "funding": [ { @@ -12680,46 +16134,51 @@ "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": "2023-04-21T14:41:17+00:00" + "time": "2025-08-04T07:05:15+00:00" }, { "name": "symfony/webpack-encore-bundle", - "version": "v2.0.1", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/symfony/webpack-encore-bundle.git", - "reference": "150fe022740fef908f4ca3d5950ce85ab040ec76" + "reference": "5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/150fe022740fef908f4ca3d5950ce85ab040ec76", - "reference": "150fe022740fef908f4ca3d5950ce85ab040ec76", + "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e", + "reference": "5b932e0feddd81aaf0ecd7d5fcd2e450e5a7817e", "shasum": "" }, "require": { "php": ">=8.1.0", - "symfony/asset": "^5.4 || ^6.2", - "symfony/config": "^5.4 || ^6.2", - "symfony/dependency-injection": "^5.4 || ^6.2", - "symfony/http-kernel": "^5.4 || ^6.2", + "symfony/asset": "^5.4 || ^6.2 || ^7.0 || ^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", - "symfony/phpunit-bridge": "^5.4 || ^6.2", - "symfony/twig-bundle": "^5.4 || ^6.2", - "symfony/web-link": "^5.4 || ^6.2" + "symfony/framework-bundle": "^5.4 || ^6.2 || ^7.0 || ^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": { "thanks": { - "name": "symfony/webpack-encore", - "url": "https://github.com/symfony/webpack-encore" + "url": "https://github.com/symfony/webpack-encore", + "name": "symfony/webpack-encore" } }, "autoload": { @@ -12737,10 +16196,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Integration with your Symfony app & Webpack Encore!", + "description": "Integration of your Symfony app with Webpack Encore", "support": { "issues": "https://github.com/symfony/webpack-encore-bundle/issues", - "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.0.1" + "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.4.0" }, "funding": [ { @@ -12751,37 +16210,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": "2023-05-31T14:28:33+00:00" + "time": "2025-11-27T13:41:46+00:00" }, { "name": "symfony/yaml", - "version": "v6.3.3", + "version": "v7.4.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "e23292e8c07c85b971b44c1c4b87af52133e2add" + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/e23292e8c07c85b971b44c1c4b87af52133e2add", - "reference": "e23292e8c07c85b971b44c1c4b87af52133e2add", + "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345", + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<5.4" + "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0" + "symfony/console": "^6.4|^7.0|^8.0" }, "bin": [ "Resources/bin/yaml-lint" @@ -12812,7 +16275,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.3.3" + "source": "https://github.com/symfony/yaml/tree/v7.4.1" }, "funding": [ { @@ -12823,25 +16286,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": "2023-07-31T07:08:24+00:00" + "time": "2025-12-04T18:11:45+00:00" }, { - "name": "tecnickcom/tc-lib-barcode", - "version": "1.17.25", + "name": "symplify/easy-coding-standard", + "version": "12.6.2", "source": { "type": "git", - "url": "https://github.com/tecnickcom/tc-lib-barcode.git", - "reference": "2b87f7c63dfd05000445a202c1779aeb9eb4549d" + "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", + "reference": "7a6798aa424f0ecafb1542b6f5207c5a99704d3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/tc-lib-barcode/zipball/2b87f7c63dfd05000445a202c1779aeb9eb4549d", - "reference": "2b87f7c63dfd05000445a202c1779aeb9eb4549d", + "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/7a6798aa424f0ecafb1542b6f5207c5a99704d3d", + "reference": "7a6798aa424f0ecafb1542b6f5207c5a99704d3d", + "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.2" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2025-10-29T08:51:50+00:00" + }, + { + "name": "tecnickcom/tc-lib-barcode", + "version": "2.4.18", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-barcode.git", + "reference": "338095651126ec4207f98e5221beea30b27c0fe9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-barcode/zipball/338095651126ec4207f98e5221beea30b27c0fe9", + "reference": "338095651126ec4207f98e5221beea30b27c0fe9", "shasum": "" }, "require": { @@ -12849,16 +16377,15 @@ "ext-date": "*", "ext-gd": "*", "ext-pcre": "*", - "php": ">=5.4", - "tecnickcom/tc-lib-color": "^1.14" + "php": ">=8.1", + "tecnickcom/tc-lib-color": "^2.3" }, "require-dev": { - "pdepend/pdepend": "2.13.0", - "phploc/phploc": "7.0.2 || 6.0.2 || 5.0.0 || 4.0.1 || 3.0.1 || 2.1.5", - "phpmd/phpmd": "2.13.0", - "phpunit/phpunit": "10.1.2 || 9.6.7 || 8.5.31 || 7.5.20 || 6.5.14 || 5.7.27 || 4.8.36", - "sebastian/phpcpd": "6.0.3 || 5.0.2 || 4.1.0 || 3.0.1 || 2.0.4", - "squizlabs/php_codesniffer": "3.7.2 || 2.9.2" + "pdepend/pdepend": "2.16.2", + "phpcompatibility/php-compatibility": "^10.0.0@dev", + "phpmd/phpmd": "2.15.0", + "phpunit/phpunit": "12.4.4 || 11.5.44 || 10.5.58", + "squizlabs/php_codesniffer": "4.0.1" }, "type": "library", "autoload": { @@ -12868,7 +16395,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0" + "LGPL-3.0-or-later" ], "authors": [ { @@ -12891,6 +16418,9 @@ "EAN 13", "EAN 8", "ECC200", + "ISO IEC 15438 2006", + "ISO IEC 16022", + "ISO IEC 24778 2008", "Intelligent Mail Barcode", "Interleaved 2 of 5", "KIX", @@ -12907,6 +16437,7 @@ "USD-3", "USPS-B-3200", "USS-93", + "aztec", "barcode", "datamatrix", "pdf417", @@ -12918,41 +16449,40 @@ ], "support": { "issues": "https://github.com/tecnickcom/tc-lib-barcode/issues", - "source": "https://github.com/tecnickcom/tc-lib-barcode/tree/1.17.25" + "source": "https://github.com/tecnickcom/tc-lib-barcode/tree/2.4.18" }, "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": "2023-05-18T08:10:11+00:00" + "time": "2025-12-11T12:48:04+00:00" }, { "name": "tecnickcom/tc-lib-color", - "version": "1.14.24", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/tecnickcom/tc-lib-color.git", - "reference": "6207533413f6edc3fea373d0e54041661d2bd905" + "reference": "4a70cf68cd9fd4082b1b6d16234876a66649be0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/tc-lib-color/zipball/6207533413f6edc3fea373d0e54041661d2bd905", - "reference": "6207533413f6edc3fea373d0e54041661d2bd905", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-color/zipball/4a70cf68cd9fd4082b1b6d16234876a66649be0b", + "reference": "4a70cf68cd9fd4082b1b6d16234876a66649be0b", "shasum": "" }, "require": { "ext-pcre": "*", - "php": ">=5.3" + "php": ">=8.1" }, "require-dev": { - "pdepend/pdepend": "2.13.0", - "phploc/phploc": "7.0.2 || 6.0.2 || 5.0.0 || 4.0.1 || 3.0.1 || 2.1.5", - "phpmd/phpmd": "2.13.0", - "phpunit/phpunit": "10.1.2 || 9.6.7 || 8.5.31 || 7.5.20 || 6.5.14 || 5.7.27 || 4.8.36", - "sebastian/phpcpd": "6.0.3 || 5.0.2 || 4.1.0 || 3.0.1 || 2.0.4", - "squizlabs/php_codesniffer": "3.7.2 || 2.9.2" + "pdepend/pdepend": "2.16.2", + "phpcompatibility/php-compatibility": "^10.0.0@dev", + "phpmd/phpmd": "2.15.0", + "phpunit/phpunit": "12.4.4 || 11.5.44 || 10.5.58", + "squizlabs/php_codesniffer": "4.0.1" }, "type": "library", "autoload": { @@ -12962,7 +16492,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0" + "LGPL-3.0-or-later" ], "authors": [ { @@ -12989,43 +16519,184 @@ ], "support": { "issues": "https://github.com/tecnickcom/tc-lib-color/issues", - "source": "https://github.com/tecnickcom/tc-lib-color/tree/1.14.24" + "source": "https://github.com/tecnickcom/tc-lib-color/tree/2.3.2" }, "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": "2023-05-18T08:09:02+00:00" + "time": "2025-12-11T12:13:39+00:00" }, { - "name": "tijsverkoyen/css-to-inline-styles", - "version": "2.2.6", + "name": "thecodingmachine/safe", + "version": "v3.3.0", "source": { "type": "git", - "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "c42125b83a4fa63b187fdf29f9c93cb7733da30c" + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/c42125b83a4fa63b187fdf29f9c93cb7733da30c", - "reference": "c42125b83a4fa63b187fdf29f9c93cb7733da30c", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/2cdd579eeaa2e78e51c7509b50cc9fb89a956236", + "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2025-05-14T06:15:44+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/f0292ccf0ec75843d65027214426b6b163b48b41", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", - "php": "^5.5 || ^7.0 || ^8.0", - "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + "php": "^7.4 || ^8.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5 || ^8.5.21 || ^9.5.10" + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^8.5.21 || ^9.5.10" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -13048,34 +16719,38 @@ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "support": { "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.6" + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.4.0" }, - "time": "2023-01-03T09:29:04+00:00" + "time": "2025-12-02T11:56:42+00:00" }, { "name": "twig/cssinliner-extra", - "version": "v3.7.0", + "version": "v3.22.0", "source": { "type": "git", "url": "https://github.com/twigphp/cssinliner-extra.git", - "reference": "85c8f3d7712bab57f6162f9637613df0511f207b" + "reference": "9bcbf04ca515e98fcde479fdceaa1d9d9e76173e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/85c8f3d7712bab57f6162f9637613df0511f207b", - "reference": "85c8f3d7712bab57f6162f9637613df0511f207b", + "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/9bcbf04ca515e98fcde479fdceaa1d9d9e76173e", + "reference": "9bcbf04ca515e98fcde479fdceaa1d9d9e76173e", "shasum": "" }, "require": { - "php": ">=7.1.3", + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", "tijsverkoyen/css-to-inline-styles": "^2.0", - "twig/twig": "^2.7|^3.0" + "twig/twig": "^3.13|^4.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "symfony/phpunit-bridge": "^6.4|^7.0" }, "type": "library", "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { "Twig\\Extra\\CssInliner\\": "" }, @@ -13103,7 +16778,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.7.0" + "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.22.0" }, "funding": [ { @@ -13115,38 +16790,38 @@ "type": "tidelift" } ], - "time": "2023-02-09T06:45:16+00:00" + "time": "2025-07-29T08:07:07+00:00" }, { "name": "twig/extra-bundle", - "version": "v3.7.0", + "version": "v3.22.2", "source": { "type": "git", "url": "https://github.com/twigphp/twig-extra-bundle.git", - "reference": "802cc2dd46ec88285d6c7fa85c26ab7f2cd5bc49" + "reference": "09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/802cc2dd46ec88285d6c7fa85c26ab7f2cd5bc49", - "reference": "802cc2dd46ec88285d6c7fa85c26ab7f2cd5bc49", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e", + "reference": "09de9be7f6c0d19ede7b5a1dbfcfb2e9d1e0ea9e", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/framework-bundle": "^4.4|^5.0|^6.0", - "symfony/twig-bundle": "^4.4|^5.0|^6.0", - "twig/twig": "^2.7|^3.0" + "php": ">=8.1.0", + "symfony/framework-bundle": "^5.4|^6.4|^7.0|^8.0", + "symfony/twig-bundle": "^5.4|^6.4|^7.0|^8.0", + "twig/twig": "^3.2|^4.0" }, "require-dev": { - "league/commonmark": "^1.0|^2.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0", + "league/commonmark": "^2.7", + "symfony/phpunit-bridge": "^6.4|^7.0", "twig/cache-extra": "^3.0", - "twig/cssinliner-extra": "^2.12|^3.0", - "twig/html-extra": "^2.12|^3.0", - "twig/inky-extra": "^2.12|^3.0", - "twig/intl-extra": "^2.12|^3.0", - "twig/markdown-extra": "^2.12|^3.0", - "twig/string-extra": "^2.12|^3.0" + "twig/cssinliner-extra": "^3.0", + "twig/html-extra": "^3.0", + "twig/inky-extra": "^3.0", + "twig/intl-extra": "^3.0", + "twig/markdown-extra": "^3.0", + "twig/string-extra": "^3.0" }, "type": "symfony-bundle", "autoload": { @@ -13177,7 +16852,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.7.0" + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.22.2" }, "funding": [ { @@ -13189,32 +16864,36 @@ "type": "tidelift" } ], - "time": "2023-05-06T11:11:46+00:00" + "time": "2025-12-05T08:51:53+00:00" }, { "name": "twig/html-extra", - "version": "v3.7.0", + "version": "v3.22.1", "source": { "type": "git", "url": "https://github.com/twigphp/html-extra.git", - "reference": "af5b336a13122d28d405714b6f2abe840632251b" + "reference": "d56d33315bce2b19ed815f8feedce85448736568" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/html-extra/zipball/af5b336a13122d28d405714b6f2abe840632251b", - "reference": "af5b336a13122d28d405714b6f2abe840632251b", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/d56d33315bce2b19ed815f8feedce85448736568", + "reference": "d56d33315bce2b19ed815f8feedce85448736568", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/mime": "^4.4|^5.0|^6.0", - "twig/twig": "^2.7|^3.0" + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/mime": "^5.4|^6.4|^7.0|^8.0", + "twig/twig": "^3.13|^4.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "symfony/phpunit-bridge": "^6.4|^7.0" }, "type": "library", "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { "Twig\\Extra\\Html\\": "" }, @@ -13241,7 +16920,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/html-extra/tree/v3.7.0" + "source": "https://github.com/twigphp/html-extra/tree/v3.22.1" }, "funding": [ { @@ -13253,32 +16932,36 @@ "type": "tidelift" } ], - "time": "2023-02-09T06:45:16+00:00" + "time": "2025-11-02T11:00:49+00:00" }, { "name": "twig/inky-extra", - "version": "v3.7.0", + "version": "v3.22.0", "source": { "type": "git", "url": "https://github.com/twigphp/inky-extra.git", - "reference": "907abf7046082cc151a3ee01f268dbf5f5f28eab" + "reference": "631f42c7123240d9c2497903679ec54bb25f2f52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/907abf7046082cc151a3ee01f268dbf5f5f28eab", - "reference": "907abf7046082cc151a3ee01f268dbf5f5f28eab", + "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/631f42c7123240d9c2497903679ec54bb25f2f52", + "reference": "631f42c7123240d9c2497903679ec54bb25f2f52", "shasum": "" }, "require": { "lorenzo/pinky": "^1.0.5", - "php": ">=7.1.3", - "twig/twig": "^2.7|^3.0" + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", + "twig/twig": "^3.13|^4.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "symfony/phpunit-bridge": "^6.4|^7.0" }, "type": "library", "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { "Twig\\Extra\\Inky\\": "" }, @@ -13307,7 +16990,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/inky-extra/tree/v3.7.0" + "source": "https://github.com/twigphp/inky-extra/tree/v3.22.0" }, "funding": [ { @@ -13319,29 +17002,29 @@ "type": "tidelift" } ], - "time": "2023-02-09T06:45:16+00:00" + "time": "2025-07-29T08:07:07+00:00" }, { "name": "twig/intl-extra", - "version": "v3.7.0", + "version": "v3.22.1", "source": { "type": "git", "url": "https://github.com/twigphp/intl-extra.git", - "reference": "a97c323bebfca009d02994a5a8568c0b412a49ab" + "reference": "93ac31e53cdd3f2e541f42690cd0c54ca8138ab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/a97c323bebfca009d02994a5a8568c0b412a49ab", - "reference": "a97c323bebfca009d02994a5a8568c0b412a49ab", + "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/93ac31e53cdd3f2e541f42690cd0c54ca8138ab1", + "reference": "93ac31e53cdd3f2e541f42690cd0c54ca8138ab1", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/intl": "^4.4|^5.0|^6.0", - "twig/twig": "^2.7|^3.0" + "php": ">=8.1.0", + "symfony/intl": "^5.4|^6.4|^7.0|^8.0", + "twig/twig": "^3.13|^4.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "symfony/phpunit-bridge": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -13371,7 +17054,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/intl-extra/tree/v3.7.0" + "source": "https://github.com/twigphp/intl-extra/tree/v3.22.1" }, "funding": [ { @@ -13383,35 +17066,39 @@ "type": "tidelift" } ], - "time": "2023-02-09T06:45:16+00:00" + "time": "2025-11-02T11:00:49+00:00" }, { "name": "twig/markdown-extra", - "version": "v3.7.0", + "version": "v3.22.0", "source": { "type": "git", "url": "https://github.com/twigphp/markdown-extra.git", - "reference": "8f1179e279cea6ef14066a4560b859df58acd5d8" + "reference": "fb6f952082e3a7d62a75c8be2c8c47242d3925fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/8f1179e279cea6ef14066a4560b859df58acd5d8", - "reference": "8f1179e279cea6ef14066a4560b859df58acd5d8", + "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/fb6f952082e3a7d62a75c8be2c8c47242d3925fb", + "reference": "fb6f952082e3a7d62a75c8be2c8c47242d3925fb", "shasum": "" }, "require": { - "php": ">=7.1.3", - "twig/twig": "^2.7|^3.0" + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", + "twig/twig": "^3.13|^4.0" }, "require-dev": { - "erusev/parsedown": "^1.7", - "league/commonmark": "^1.0|^2.0", + "erusev/parsedown": "dev-master as 1.x-dev", + "league/commonmark": "^2.7", "league/html-to-markdown": "^4.8|^5.0", "michelf/php-markdown": "^1.8|^2.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "symfony/phpunit-bridge": "^6.4|^7.0" }, "type": "library", "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { "Twig\\Extra\\Markdown\\": "" }, @@ -13439,7 +17126,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/markdown-extra/tree/v3.7.0" + "source": "https://github.com/twigphp/markdown-extra/tree/v3.22.0" }, "funding": [ { @@ -13451,33 +17138,108 @@ "type": "tidelift" } ], - "time": "2023-02-09T06:45:16+00:00" + "time": "2025-09-15T05:57:37+00:00" }, { - "name": "twig/twig", - "version": "v3.7.0", + "name": "twig/string-extra", + "version": "v3.22.1", "source": { "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "5cf942bbab3df42afa918caeba947f1b690af64b" + "url": "https://github.com/twigphp/string-extra.git", + "reference": "d5f16e0bec548bc96cce255b5f43d90492b8ce13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/5cf942bbab3df42afa918caeba947f1b690af64b", - "reference": "5cf942bbab3df42afa918caeba947f1b690af64b", + "url": "https://api.github.com/repos/twigphp/string-extra/zipball/d5f16e0bec548bc96cce255b5f43d90492b8ce13", + "reference": "d5f16e0bec548bc96cce255b5f43d90492b8ce13", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1.0", + "symfony/string": "^5.4|^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^1.1|^2|^3", + "twig/twig": "^3.13|^4.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Twig\\Extra\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Twig extension for Symfony String", + "homepage": "https://twig.symfony.com", + "keywords": [ + "html", + "string", + "twig", + "unicode" + ], + "support": { + "source": "https://github.com/twigphp/string-extra/tree/v3.22.1" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2025-11-02T11:00:49+00:00" + }, + { + "name": "twig/twig", + "version": "v3.22.2", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/946ddeafa3c9f4ce279d1f34051af041db0e16f2", + "reference": "946ddeafa3c9f4ce279d1f34051af041db0e16f2", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { + "phpstan/phpstan": "^2.0", "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, "type": "library", "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], "psr-4": { "Twig\\": "src/" } @@ -13510,7 +17272,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.7.0" + "source": "https://github.com/twigphp/Twig/tree/v3.22.2" }, "funding": [ { @@ -13522,20 +17284,20 @@ "type": "tidelift" } ], - "time": "2023-07-26T07:16:09+00:00" + "time": "2025-12-14T11:28:47+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": { @@ -13583,45 +17345,44 @@ "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.2.3", + "version": "4.4.2", "source": { "type": "git", "url": "https://github.com/web-auth/cose-lib.git", - "reference": "0ecad86d2d034ea22e2205d81c8cdec13d93a991" + "reference": "a93b61c48fb587855f64a9ec11ad7b60e867cb15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/0ecad86d2d034ea22e2205d81c8cdec13d93a991", - "reference": "0ecad86d2d034ea22e2205d81c8cdec13d93a991", + "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/a93b61c48fb587855f64a9ec11ad7b60e867cb15", + "reference": "a93b61c48fb587855f64a9ec11ad7b60e867cb15", "shasum": "" }, "require": { - "brick/math": "^0.9|^0.10|^0.11", + "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13", "ext-json": "*", - "ext-mbstring": "*", "ext-openssl": "*", "php": ">=8.1", "spomky-labs/pki-framework": "^1.0" }, "require-dev": { - "ekino/phpstan-banned-code": "^1.0", - "infection/infection": "^0.27", + "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", - "qossmic/deptrac-shim": "^1.0", - "rector/rector": "^0.17", - "symfony/phpunit-bridge": "^6.1", + "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" }, "suggest": { @@ -13656,7 +17417,7 @@ ], "support": { "issues": "https://github.com/web-auth/cose-lib/issues", - "source": "https://github.com/web-auth/cose-lib/tree/4.2.3" + "source": "https://github.com/web-auth/cose-lib/tree/4.4.2" }, "funding": [ { @@ -13668,134 +17429,51 @@ "type": "patreon" } ], - "time": "2023-07-26T13:32:03+00:00" - }, - { - "name": "web-auth/metadata-service", - "version": "4.6.4", - "source": { - "type": "git", - "url": "https://github.com/web-auth/webauthn-metadata-service.git", - "reference": "b58fbb0df46450acc426329bd87b60d794859da0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/b58fbb0df46450acc426329bd87b60d794859da0", - "reference": "b58fbb0df46450acc426329bd87b60d794859da0", - "shasum": "" - }, - "require": { - "ext-json": "*", - "lcobucci/clock": "^2.2|^3.0", - "paragonie/constant_time_encoding": "^2.6", - "php": ">=8.1", - "psr/clock": "^1.0", - "psr/event-dispatcher": "^1.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", - "psr/log": "^1.0|^2.0|^3.0", - "spomky-labs/pki-framework": "^1.0", - "symfony/deprecation-contracts": "^3.2" - }, - "suggest": { - "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", - "web-token/jwt-key-mgmt": "Mandatory for fetching Metadata Statement from distant sources", - "web-token/jwt-signature-algorithm-ecdsa": "Mandatory for fetching Metadata Statement from distant sources" - }, - "type": "library", - "extra": { - "thanks": { - "name": "web-auth/webauthn-framework", - "url": "https://github.com/web-auth/webauthn-framework" - } - }, - "autoload": { - "psr-4": { - "Webauthn\\MetadataService\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Florent Morselli", - "homepage": "https://github.com/Spomky" - }, - { - "name": "All contributors", - "homepage": "https://github.com/web-auth/metadata-service/contributors" - } - ], - "description": "Metadata Service for FIDO2/Webauthn", - "homepage": "https://github.com/web-auth", - "keywords": [ - "FIDO2", - "fido", - "webauthn" - ], - "support": { - "source": "https://github.com/web-auth/webauthn-metadata-service/tree/4.6.4" - }, - "funding": [ - { - "url": "https://github.com/Spomky", - "type": "github" - }, - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "time": "2023-06-01T19:06:30+00:00" + "time": "2025-08-14T20:33:29+00:00" }, { "name": "web-auth/webauthn-lib", - "version": "4.6.4", + "version": "5.2.3", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", - "reference": "8cb4949d81ef8414c82f334fb3514141aa013340" + "reference": "8782f575032fedc36e2eb27c39c736054e2b6867" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/8cb4949d81ef8414c82f334fb3514141aa013340", - "reference": "8cb4949d81ef8414c82f334fb3514141aa013340", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/8782f575032fedc36e2eb27c39c736054e2b6867", + "reference": "8782f575032fedc36e2eb27c39c736054e2b6867", "shasum": "" }, "require": { "ext-json": "*", - "ext-mbstring": "*", "ext-openssl": "*", - "paragonie/constant_time_encoding": "^2.6", - "php": ">=8.1", + "paragonie/constant_time_encoding": "^2.6|^3.0", + "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", - "symfony/uid": "^6.1", - "web-auth/cose-lib": "^4.0.12", - "web-auth/metadata-service": "self.version" - }, - "require-dev": { - "symfony/event-dispatcher": "^6.1" + "spomky-labs/pki-framework": "^1.0", + "symfony/clock": "^6.4|^7.0", + "symfony/deprecation-contracts": "^3.2", + "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": { "psr/log-implementation": "Recommended to receive logs from the library", "symfony/event-dispatcher": "Recommended to use dispatched events", - "web-token/jwt-key-mgmt": "Mandatory for the AndroidSafetyNet Attestation Statement support", - "web-token/jwt-signature-algorithm-ecdsa": "Recommended for the AndroidSafetyNet Attestation Statement support", - "web-token/jwt-signature-algorithm-eddsa": "Recommended for the AndroidSafetyNet Attestation Statement support", - "web-token/jwt-signature-algorithm-rsa": "Mandatory for the AndroidSafetyNet Attestation Statement support" + "web-token/jwt-library": "Mandatory for fetching Metadata Statement from distant sources" }, "type": "library", "extra": { "thanks": { - "name": "web-auth/webauthn-framework", - "url": "https://github.com/web-auth/webauthn-framework" + "url": "https://github.com/web-auth/webauthn-framework", + "name": "web-auth/webauthn-framework" } }, "autoload": { @@ -13825,7 +17503,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/4.6.4" + "source": "https://github.com/web-auth/webauthn-lib/tree/5.2.3" }, "funding": [ { @@ -13837,45 +17515,44 @@ "type": "patreon" } ], - "time": "2023-07-15T14:53:06+00:00" + "time": "2025-12-20T10:54:02+00:00" }, { "name": "web-auth/webauthn-symfony-bundle", - "version": "4.6.4", + "version": "5.2.3", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-symfony-bundle.git", - "reference": "04bd26182e26c8bf218bdca3d8b0f569ff983ff8" + "reference": "91f0aff70703e20d84251c83e238da1f8fc53b24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/04bd26182e26c8bf218bdca3d8b0f569ff983ff8", - "reference": "04bd26182e26c8bf218bdca3d8b0f569ff983ff8", + "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/91f0aff70703e20d84251c83e238da1f8fc53b24", + "reference": "91f0aff70703e20d84251c83e238da1f8fc53b24", "shasum": "" }, "require": { - "nyholm/psr7": "^1.5", - "php": ">=8.1", + "php": ">=8.2", "psr/event-dispatcher": "^1.0", - "spomky-labs/cbor-bundle": "^3.0", - "symfony/config": "^6.1", - "symfony/dependency-injection": "^6.1", - "symfony/framework-bundle": "^6.1", - "symfony/http-client": "^6.1", - "symfony/psr-http-message-bridge": "^2.1", - "symfony/security-bundle": "^6.1", - "symfony/security-core": "^6.1", - "symfony/security-http": "^6.1", - "symfony/serializer": "^6.1", - "symfony/validator": "^6.1", - "web-auth/webauthn-lib": "self.version", - "web-token/jwt-signature": "^3.1" + "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": { "thanks": { - "name": "web-auth/webauthn-framework", - "url": "https://github.com/web-auth/webauthn-framework" + "url": "https://github.com/web-auth/webauthn-framework", + "name": "web-auth/webauthn-framework" } }, "autoload": { @@ -13901,11 +17578,14 @@ "homepage": "https://github.com/web-auth", "keywords": [ "FIDO2", + "bundle", "fido", + "symfony", + "symfony-bundle", "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.6.4" + "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/5.2.3" }, "funding": [ { @@ -13917,190 +17597,37 @@ "type": "patreon" } ], - "time": "2023-06-12T14:32:32+00:00" - }, - { - "name": "web-token/jwt-core", - "version": "3.2.8", - "source": { - "type": "git", - "url": "https://github.com/web-token/jwt-core.git", - "reference": "2bc6e99a60910d0f495682acd8b23d3eef9865a3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-core/zipball/2bc6e99a60910d0f495682acd8b23d3eef9865a3", - "reference": "2bc6e99a60910d0f495682acd8b23d3eef9865a3", - "shasum": "" - }, - "require": { - "brick/math": "^0.9|^0.10|^0.11", - "ext-json": "*", - "ext-mbstring": "*", - "paragonie/constant_time_encoding": "^2.4", - "php": ">=8.1", - "spomky-labs/pki-framework": "^1.0" - }, - "conflict": { - "spomky-labs/jose": "*" - }, - "type": "library", - "autoload": { - "psr-4": { - "Jose\\Component\\Core\\": "" - } - }, - "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": "Core component of the JWT Framework.", - "homepage": "https://github.com/web-token", - "keywords": [ - "JOSE", - "JWE", - "JWK", - "JWKSet", - "JWS", - "Jot", - "RFC7515", - "RFC7516", - "RFC7517", - "RFC7518", - "RFC7519", - "RFC7520", - "bundle", - "jwa", - "jwt", - "symfony" - ], - "support": { - "source": "https://github.com/web-token/jwt-core/tree/3.2.8" - }, - "funding": [ - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "time": "2023-08-23T09:49:09+00:00" - }, - { - "name": "web-token/jwt-signature", - "version": "3.2.8", - "source": { - "type": "git", - "url": "https://github.com/web-token/jwt-signature.git", - "reference": "156e0b0ef534e53eecf23a32a92ee6d8cb4fdac4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-signature/zipball/156e0b0ef534e53eecf23a32a92ee6d8cb4fdac4", - "reference": "156e0b0ef534e53eecf23a32a92ee6d8cb4fdac4", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "web-token/jwt-core": "^3.2" - }, - "suggest": { - "web-token/jwt-signature-algorithm-ecdsa": "ECDSA Based Signature Algorithms", - "web-token/jwt-signature-algorithm-eddsa": "EdDSA Based Signature Algorithms", - "web-token/jwt-signature-algorithm-experimental": "Experimental Signature Algorithms", - "web-token/jwt-signature-algorithm-hmac": "HMAC Based Signature Algorithms", - "web-token/jwt-signature-algorithm-none": "None Signature Algorithm", - "web-token/jwt-signature-algorithm-rsa": "RSA Based Signature Algorithms" - }, - "type": "library", - "autoload": { - "psr-4": { - "Jose\\Component\\Signature\\": "" - } - }, - "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-signature/contributors" - } - ], - "description": "Signature component of the JWT Framework.", - "homepage": "https://github.com/web-token", - "keywords": [ - "JOSE", - "JWE", - "JWK", - "JWKSet", - "JWS", - "Jot", - "RFC7515", - "RFC7516", - "RFC7517", - "RFC7518", - "RFC7519", - "RFC7520", - "bundle", - "jwa", - "jwt", - "symfony" - ], - "support": { - "source": "https://github.com/web-token/jwt-signature/tree/3.2.8" - }, - "funding": [ - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "time": "2023-05-18T16:20:51+00:00" + "time": "2025-12-20T10:20:41+00:00" }, { "name": "webmozart/assert", - "version": "1.11.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + "reference": "1b34b004e35a164bc5bb6ebd33c844b2d8069a54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/1b34b004e35a164bc5bb6ebd33c844b2d8069a54", + "reference": "1b34b004e35a164bc5bb6ebd33c844b2d8069a54", "shasum": "" }, "require": { "ext-ctype": "*", - "php": "^7.2 || ^8.0" + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.10-dev" + "dev-feature/2-0": "2.0-dev" } }, "autoload": { @@ -14116,6 +17643,10 @@ { "name": "Bernhard Schussek", "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" } ], "description": "Assertions to validate method input/output with nice error messages.", @@ -14126,437 +17657,110 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" + "source": "https://github.com/webmozarts/assert/tree/2.0.0" }, - "time": "2022-06-03T18:03:27+00:00" + "time": "2025-12-16T21:36:00+00:00" + }, + { + "name": "willdurand/negotiation", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/willdurand/Negotiation.git", + "reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/68e9ea0553ef6e2ee8db5c1d98829f111e623ec2", + "reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Negotiation\\": "src/Negotiation" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "William Durand", + "email": "will+git@drnd.me" + } + ], + "description": "Content Negotiation tools for PHP provided as a standalone library.", + "homepage": "http://williamdurand.fr/Negotiation/", + "keywords": [ + "accept", + "content", + "format", + "header", + "negotiation" + ], + "support": { + "issues": "https://github.com/willdurand/Negotiation/issues", + "source": "https://github.com/willdurand/Negotiation/tree/3.1.0" + }, + "time": "2022-01-30T20:08:53+00:00" } ], "packages-dev": [ - { - "name": "amphp/amp", - "version": "v2.6.2", - "source": { - "type": "git", - "url": "https://github.com/amphp/amp.git", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1", - "ext-json": "*", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^7 | ^8 | ^9", - "psalm/phar": "^3.11@dev", - "react/promise": "^2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "files": [ - "lib/functions.php", - "lib/Internal/functions.php" - ], - "psr-4": { - "Amp\\": "lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Daniel Lowrey", - "email": "rdlowrey@php.net" - }, - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Bob Weinand", - "email": "bobwei9@hotmail.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "A non-blocking concurrency framework for PHP applications.", - "homepage": "https://amphp.org/amp", - "keywords": [ - "async", - "asynchronous", - "awaitable", - "concurrency", - "event", - "event-loop", - "future", - "non-blocking", - "promise" - ], - "support": { - "irc": "irc://irc.freenode.org/amphp", - "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.2" - }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2022-02-20T17:52:18+00:00" - }, - { - "name": "amphp/byte-stream", - "version": "v1.8.1", - "source": { - "type": "git", - "url": "https://github.com/amphp/byte-stream.git", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", - "shasum": "" - }, - "require": { - "amphp/amp": "^2", - "php": ">=7.1" - }, - "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1.4", - "friendsofphp/php-cs-fixer": "^2.3", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^6 || ^7 || ^8", - "psalm/phar": "^3.11.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "files": [ - "lib/functions.php" - ], - "psr-4": { - "Amp\\ByteStream\\": "lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "http://amphp.org/byte-stream", - "keywords": [ - "amp", - "amphp", - "async", - "io", - "non-blocking", - "stream" - ], - "support": { - "irc": "irc://irc.freenode.org/amphp", - "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" - }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2021-03-30T17:13:30+00:00" - }, - { - "name": "composer/pcre", - "version": "3.1.0", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", - "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.3", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-11-17T09:50:14+00:00" - }, - { - "name": "composer/semver", - "version": "3.3.2", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/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": "2022-04-01T19:23:25+00:00" - }, - { - "name": "composer/xdebug-handler", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "ced299686f41dce890debac69273b47ffe98a40c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", - "reference": "ced299686f41dce890debac69273b47ffe98a40c", - "shasum": "" - }, - "require": { - "composer/pcre": "^1 || ^2 || ^3", - "php": "^7.2.5 || ^8.0", - "psr/log": "^1 || ^2 || ^3" - }, - "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" - }, - "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": "2022-02-25T21:32:43+00:00" - }, { "name": "dama/doctrine-test-bundle", - "version": "v7.2.1", + "version": "v8.4.1", "source": { "type": "git", "url": "https://github.com/dmaicher/doctrine-test-bundle.git", - "reference": "175b47153609a369117d97d36049b8a8c3b69dc1" + "reference": "d9f4fb01a43da2e279ca190fa25ab9e26f15a0c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/175b47153609a369117d97d36049b8a8c3b69dc1", - "reference": "175b47153609a369117d97d36049b8a8c3b69dc1", + "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/d9f4fb01a43da2e279ca190fa25ab9e26f15a0c8", + "reference": "d9f4fb01a43da2e279ca190fa25ab9e26f15a0c8", "shasum": "" }, "require": { - "doctrine/dbal": "^3.3", - "doctrine/doctrine-bundle": "^2.2.2", - "ext-json": "*", - "php": "^7.3 || ^8.0", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^5.4 || ^6.0", - "symfony/framework-bundle": "^5.4 || ^6.0" + "doctrine/dbal": "^3.3 || ^4.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", - "doctrine/cache": "^1.12", - "phpstan/phpstan": "^1.2", - "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0", - "symfony/phpunit-bridge": "^6.0", - "symfony/process": "^5.4 || ^6.0", - "symfony/yaml": "^5.4 || ^6.0" + "friendsofphp/php-cs-fixer": "^3.27", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.5.57 || ^11.5.41|| ^12.3.14", + "symfony/dotenv": "^6.4 || ^7.3 || ^8.0", + "symfony/process": "^6.4 || ^7.3 || ^8.0" }, "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "7.x-dev" + "dev-master": "8.x-dev" } }, "autoload": { "psr-4": { - "DAMA\\DoctrineTestBundle\\": "src/DAMA/DoctrineTestBundle" + "DAMA\\DoctrineTestBundle\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -14575,88 +17779,55 @@ "isolation", "performance", "symfony", + "testing", "tests" ], "support": { "issues": "https://github.com/dmaicher/doctrine-test-bundle/issues", - "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v7.2.1" + "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v8.4.1" }, - "time": "2023-02-07T10:02:27+00:00" - }, - { - "name": "dnoegel/php-xdg-base-dir", - "version": "v0.1.1", - "source": { - "type": "git", - "url": "https://github.com/dnoegel/php-xdg-base-dir.git", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "XdgBaseDir\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "implementation of xdg base directory specification for php", - "support": { - "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", - "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" - }, - "time": "2019-12-04T15:06:13+00:00" + "time": "2025-12-07T21:48:15+00:00" }, { "name": "doctrine/doctrine-fixtures-bundle", - "version": "3.4.4", + "version": "4.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineFixturesBundle.git", - "reference": "9ec3139c52a42e94c9fd1e95f8d2bca94326edfb" + "reference": "9e013ed10d49bf7746b07204d336384a7d9b5a4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/9ec3139c52a42e94c9fd1e95f8d2bca94326edfb", - "reference": "9ec3139c52a42e94c9fd1e95f8d2bca94326edfb", + "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/9e013ed10d49bf7746b07204d336384a7d9b5a4d", + "reference": "9e013ed10d49bf7746b07204d336384a7d9b5a4d", "shasum": "" }, "require": { - "doctrine/data-fixtures": "^1.3", - "doctrine/doctrine-bundle": "^1.11|^2.0", - "doctrine/orm": "^2.6.0", - "doctrine/persistence": "^1.3.7|^2.0|^3.0", - "php": "^7.1 || ^8.0", - "symfony/config": "^3.4|^4.3|^5.0|^6.0", - "symfony/console": "^3.4|^4.3|^5.0|^6.0", - "symfony/dependency-injection": "^3.4.47|^4.3|^5.0|^6.0", - "symfony/doctrine-bridge": "^3.4|^4.1|^5.0|^6.0", - "symfony/http-kernel": "^3.4|^4.3|^5.0|^6.0" + "doctrine/data-fixtures": "^2.2", + "doctrine/doctrine-bundle": "^2.2 || ^3.0", + "doctrine/orm": "^2.14.0 || ^3.0", + "doctrine/persistence": "^2.4 || ^3.0 || ^4.0", + "php": "^8.1", + "psr/log": "^2 || ^3", + "symfony/config": "^6.4 || ^7.0 || ^8.0", + "symfony/console": "^6.4 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/doctrine-bridge": "^6.4.16 || ^7.1.9 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0" + }, + "conflict": { + "doctrine/dbal": "< 3" }, "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "^1.4.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", - "symfony/phpunit-bridge": "^6.0.8", - "vimeo/psalm": "^4.22" + "doctrine/coding-standard": "14.0.0", + "phpstan/phpstan": "2.1.11", + "phpunit/phpunit": "^10.5.38 || 11.4.14" }, "type": "symfony-bundle", "autoload": { "psr-4": { - "Doctrine\\Bundle\\FixturesBundle\\": "" + "Doctrine\\Bundle\\FixturesBundle\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -14685,7 +17856,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineFixturesBundle/issues", - "source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/3.4.4" + "source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/4.3.1" }, "funding": [ { @@ -14701,43 +17872,43 @@ "type": "tidelift" } ], - "time": "2023-05-02T15:12:16+00:00" + "time": "2025-12-03T16:05:42+00:00" }, { "name": "ekino/phpstan-banned-code", - "version": "v1.0.0", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/ekino/phpstan-banned-code.git", - "reference": "4f0d7c8a0c9f5d222ffc24234aa6c5b3b71bf4c3" + "reference": "27122aa1783d6521e500c0c397c53244cfbde26f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ekino/phpstan-banned-code/zipball/4f0d7c8a0c9f5d222ffc24234aa6c5b3b71bf4c3", - "reference": "4f0d7c8a0c9f5d222ffc24234aa6c5b3b71bf4c3", + "url": "https://api.github.com/repos/ekino/phpstan-banned-code/zipball/27122aa1783d6521e500c0c397c53244cfbde26f", + "reference": "27122aa1783d6521e500c0c397c53244cfbde26f", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0", - "phpstan/phpstan": "^1.0" + "php": "^8.1", + "phpstan/phpstan": "^2.0" }, "require-dev": { "ergebnis/composer-normalize": "^2.6", "friendsofphp/php-cs-fixer": "^3.0", "nikic/php-parser": "^4.3", - "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^9.5", "symfony/var-dumper": "^5.0" }, "type": "phpstan-extension", "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, "phpstan": { "includes": [ "extension.neon" ] + }, + "branch-alias": { + "dev-master": "1.0-dev" } }, "autoload": { @@ -14760,147 +17931,50 @@ "homepage": "https://github.com/ekino/phpstan-banned-code", "keywords": [ "PHPStan", - "code quality" + "code quality", + "static analysis" ], "support": { "issues": "https://github.com/ekino/phpstan-banned-code/issues", - "source": "https://github.com/ekino/phpstan-banned-code/tree/v1.0.0" + "source": "https://github.com/ekino/phpstan-banned-code/tree/v3.0.0" }, - "time": "2021-11-02T08:37:34+00:00" + "time": "2024-11-13T09:57:22+00:00" }, { - "name": "felixfbecker/advanced-json-rpc", - "version": "v3.2.1", + "name": "jbtronics/translation-editor-bundle", + "version": "v1.1.3", "source": { "type": "git", - "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" + "url": "https://github.com/jbtronics/translation-editor-bundle.git", + "reference": "36bfb256e11d231d185bc2491323b041ba731257" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "url": "https://api.github.com/repos/jbtronics/translation-editor-bundle/zipball/36bfb256e11d231d185bc2491323b041ba731257", + "reference": "36bfb256e11d231d185bc2491323b041ba731257", "shasum": "" }, "require": { - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "php": "^7.1 || ^8.0", - "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + "ext-json": "*", + "php": "^8.1", + "symfony/deprecation-contracts": "^3.4", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/translation": "^7.0|^6.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/twig-bundle": "^7.0|^6.4|^8.0", + "symfony/web-profiler-bundle": "^7.0|^6.4|^8.0" }, "require-dev": { - "phpunit/phpunit": "^7.0 || ^8.0" + "ekino/phpstan-banned-code": "^1.0", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-strict-rules": "^1.5", + "roave/security-advisories": "dev-latest" }, - "type": "library", + "type": "symfony-bundle", "autoload": { "psr-4": { - "AdvancedJsonRpc\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], - "description": "A more advanced JSONRPC implementation", - "support": { - "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", - "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" - }, - "time": "2021-06-11T22:34:44+00:00" - }, - { - "name": "felixfbecker/language-server-protocol", - "version": "v1.5.2", - "source": { - "type": "git", - "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpstan/phpstan": "*", - "squizlabs/php_codesniffer": "^3.1", - "vimeo/psalm": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "LanguageServerProtocol\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], - "description": "PHP classes for the Language Server Protocol", - "keywords": [ - "language", - "microsoft", - "php", - "server" - ], - "support": { - "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" - }, - "time": "2022-03-02T22:36:06+00:00" - }, - { - "name": "fidry/cpu-core-counter", - "version": "0.5.1", - "source": { - "type": "git", - "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/b58e5a3933e541dc286cc91fc4f3898bbc6f1623", - "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "fidry/makefile": "^0.2.0", - "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^1.9.2", - "phpstan/phpstan-deprecation-rules": "^1.0.0", - "phpstan/phpstan-phpunit": "^1.2.2", - "phpstan/phpstan-strict-rules": "^1.4.4", - "phpunit/phpunit": "^9.5.26 || ^8.5.31", - "theofidry/php-cs-fixer-config": "^1.0", - "webmozarts/strict-phpunit": "^7.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Fidry\\CpuCoreCounter\\": "src/" + "Jbtronics\\TranslationEditorBundle\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14909,96 +17983,287 @@ ], "authors": [ { - "name": "Théo FIDRY", - "email": "theo.fidry@gmail.com" + "name": "Jan Böhmer", + "email": "mail@jan-boehmer.de" } ], - "description": "Tiny utility to get the number of CPU cores.", + "description": "A symfony bundle to allow editing translations in the symfony profiler", "keywords": [ - "CPU", - "core" + "profiler", + "symfony", + "symfony-bundle", + "translations" ], "support": { - "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/0.5.1" + "issues": "https://github.com/jbtronics/translation-editor-bundle/issues", + "source": "https://github.com/jbtronics/translation-editor-bundle/tree/v1.1.3" }, "funding": [ { - "url": "https://github.com/theofidry", + "url": "https://www.paypal.me/do9jhb", + "type": "custom" + }, + { + "url": "https://github.com/jbtronics", "type": "github" } ], - "time": "2022-12-24T12:35:10+00:00" + "time": "2025-11-30T22:23:47+00:00" }, { - "name": "netresearch/jsonmapper", - "version": "v4.2.0", + "name": "myclabs/deep-copy", + "version": "1.13.4", "source": { "type": "git", - "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "f60565f8c0566a31acf06884cdaa591867ecc956" + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/f60565f8c0566a31acf06884cdaa591867ecc956", - "reference": "f60565f8c0566a31acf06884cdaa591867ecc956", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=7.1" + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { - "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0", - "squizlabs/php_codesniffer": "~3.5" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { - "psr-0": { - "JsonMapper": "src/" + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "OSL-3.0" + "MIT" ], - "authors": [ + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ { - "name": "Christian Weiske", - "email": "cweiske@cweiske.de", - "homepage": "http://github.com/cweiske/jsonmapper/", - "role": "Developer" + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" } ], - "description": "Map nested JSON structures onto PHP classes", - "support": { - "email": "cweiske@cweiske.de", - "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.2.0" - }, - "time": "2023-04-09T17:37:40+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { - "name": "phpstan/extension-installer", - "version": "1.3.1", + "name": "nikic/php-parser", + "version": "v5.7.0", "source": { "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "f45734bfb9984c6c56c4486b71230355f066a58a" + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f45734bfb9984c6c56c4486b71230355f066a58a", - "reference": "f45734bfb9984c6c56c4486b71230355f066a58a", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", "shasum": "" }, "require": { "composer-plugin-api": "^2.0", "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.9.0" + "phpstan/phpstan": "^1.9.0 || ^2.0" }, "require-dev": { "composer/composer": "^2.0", @@ -15019,28 +18284,27 @@ "MIT" ], "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], "support": { "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.3.1" + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" }, - "time": "2023-05-24T08:59:17+00:00" + "time": "2024-09-04T20:21:43+00:00" }, { "name": "phpstan/phpstan", - "version": "1.10.30", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "2910afdd3fe33e5afd71c09f3fb0d0845b48c410" - }, + "version": "2.1.33", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2910afdd3fe33e5afd71c09f3fb0d0845b48c410", - "reference": "2910afdd3fe33e5afd71c09f3fb0d0845b48c410", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", + "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -15079,31 +18343,27 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2023-08-22T13:48:25+00:00" + "time": "2025-12-05T10:24:31+00:00" }, { "name": "phpstan/phpstan-doctrine", - "version": "1.3.42", + "version": "2.0.12", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "e4678fa1055bfd7fad052506b422aeae35fc6f63" + "reference": "d20ee0373d22735271f1eb4d631856b5f847d399" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/e4678fa1055bfd7fad052506b422aeae35fc6f63", - "reference": "e4678fa1055bfd7fad052506b422aeae35fc6f63", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/d20ee0373d22735271f1eb4d631856b5f847d399", + "reference": "d20ee0373d22735271f1eb4d631856b5f847d399", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10.12" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.13" }, "conflict": { "doctrine/collections": "<1.0", @@ -15113,24 +18373,27 @@ "doctrine/persistence": "<1.3" }, "require-dev": { + "cache/array-adapter": "^1.1", "composer/semver": "^3.3.2", - "doctrine/annotations": "^1.11.0", - "doctrine/collections": "^1.6", + "cweagans/composer-patches": "^1.7.3", + "doctrine/annotations": "^2.0", + "doctrine/collections": "^1.6 || ^2.1", "doctrine/common": "^2.7 || ^3.0", - "doctrine/dbal": "^2.13.8 || ^3.3.3", - "doctrine/lexer": "^1.2.1", - "doctrine/mongodb-odm": "^1.3 || ^2.1", - "doctrine/orm": "^2.11.0", - "doctrine/persistence": "^1.3.8 || ^2.2.1", + "doctrine/dbal": "^3.3.8", + "doctrine/lexer": "^2.0 || ^3.0", + "doctrine/mongodb-odm": "^2.4.3", + "doctrine/orm": "^2.16.0", + "doctrine/persistence": "^2.2.1 || ^3.2", "gedmo/doctrine-extensions": "^3.8", "nesbot/carbon": "^2.49", - "nikic/php-parser": "^4.13.2", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.3.13", - "phpstan/phpstan-strict-rules": "^1.5.1", - "phpunit/phpunit": "^9.5.10", - "ramsey/uuid-doctrine": "^1.5.0", - "symfony/cache": "^4.4.35" + "phpstan/phpstan-deprecation-rules": "^2.0.2", + "phpstan/phpstan-phpunit": "^2.0.8", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6.20", + "ramsey/uuid": "^4.2", + "symfony/cache": "^5.4", + "symfony/uid": "^5.4 || ^6.4 || ^7.3" }, "type": "phpstan-extension", "extra": { @@ -15153,34 +18416,33 @@ "description": "Doctrine extensions for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.3.42" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.12" }, - "time": "2023-08-09T08:21:24+00:00" + "time": "2025-12-01T11:34:02+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.5.1", + "version": "2.0.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6" + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b21c03d4f6f3a446e4311155f4be9d65048218e6", - "reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/d6211c46213d4181054b3d77b10a5c5cb0d59538", + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.29" }, "require-dev": { - "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-deprecation-rules": "^1.1", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" }, "type": "phpstan-extension", "extra": { @@ -15202,39 +18464,38 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.1" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.7" }, - "time": "2023-03-29T14:47:40+00:00" + "time": "2025-09-26T11:19:08+00:00" }, { "name": "phpstan/phpstan-symfony", - "version": "1.3.2", + "version": "2.0.9", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "7332b90dfc291ac5b4b83fbca2081936faa1e3f9" + "reference": "24d8c157aa483141b0579d705ef0aac9e1b95436" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/7332b90dfc291ac5b4b83fbca2081936faa1e3f9", - "reference": "7332b90dfc291ac5b4b83fbca2081936faa1e3f9", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/24d8c157aa483141b0579d705ef0aac9e1b95436", + "reference": "24d8c157aa483141b0579d705ef0aac9e1b95436", "shasum": "" }, "require": { "ext-simplexml": "*", - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.9.18" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.13" }, "conflict": { "symfony/framework-bundle": "<3.0" }, "require-dev": { - "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^8.5.29 || ^9.5", - "psr/container": "1.0 || 1.1.1", + "phpstan/phpstan-phpunit": "^2.0.8", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "psr/container": "1.1.2", "symfony/config": "^5.4 || ^6.1", "symfony/console": "^5.4 || ^6.1", "symfony/dependency-injection": "^5.4 || ^6.1", @@ -15243,7 +18504,8 @@ "symfony/http-foundation": "^5.4 || ^6.1", "symfony/messenger": "^5.4", "symfony/polyfill-php80": "^1.24", - "symfony/serializer": "^5.4" + "symfony/serializer": "^5.4", + "symfony/service-contracts": "^2.2.0" }, "type": "phpstan-extension", "extra": { @@ -15273,92 +18535,471 @@ "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/1.3.2" + "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.9" }, - "time": "2023-05-16T12:46:15+00:00" + "time": "2025-11-29T11:17:28+00:00" }, { - "name": "psalm/plugin-symfony", - "version": "v5.0.3", + "name": "phpunit/php-code-coverage", + "version": "11.0.12", "source": { "type": "git", - "url": "https://github.com/psalm/psalm-plugin-symfony.git", - "reference": "a6cef9c701686d17d4254b544d05345e9d3e0b88" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psalm/psalm-plugin-symfony/zipball/a6cef9c701686d17d4254b544d05345e9d3e0b88", - "reference": "a6cef9c701686d17d4254b544d05345e9d3e0b88", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", "shasum": "" }, "require": { - "ext-simplexml": "*", - "php": "^7.4 || ^8.0", - "symfony/framework-bundle": "^5.0 || ^6.0", - "vimeo/psalm": "^5.1" + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.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.1", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.3.1" }, "require-dev": { - "doctrine/annotations": "^1.8|^2", - "doctrine/orm": "^2.9", - "phpunit/phpunit": "~7.5 || ~9.5", - "symfony/cache-contracts": "^1.0 || ^2.0", - "symfony/console": "*", - "symfony/form": "^5.0 || ^6.0", - "symfony/messenger": "^5.0 || ^6.0", - "symfony/security-guard": "*", - "symfony/serializer": "^5.0 || ^6.0", - "symfony/validator": "*", - "twig/twig": "^2.10 || ^3.0", - "weirdan/codeception-psalm-module": "dev-master" + "phpunit/phpunit": "^11.5.46" }, "suggest": { - "weirdan/doctrine-psalm-plugin": "If Doctrine is used, it is recommended install this plugin" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, - "type": "psalm-plugin", + "type": "library", "extra": { - "psalm": { - "pluginClass": "Psalm\\SymfonyPsalmPlugin\\Plugin" + "branch-alias": { + "dev-main": "11.0.x-dev" } }, "autoload": { - "psr-4": { - "Psalm\\SymfonyPsalmPlugin\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Farhad Safarov", - "email": "farhad.safarov@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Psalm Plugin for Symfony", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], "support": { - "issues": "https://github.com/psalm/psalm-plugin-symfony/issues", - "source": "https://github.com/psalm/psalm-plugin-symfony/tree/v5.0.3" + "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/11.0.12" }, - "time": "2023-04-21T15:40:12+00:00" + "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": "2025-12-24T07:01:01+00:00" }, { - "name": "rector/rector", - "version": "0.18.0", + "name": "phpunit/php-file-iterator", + "version": "5.1.0", "source": { "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "758ada29b5c80d933f906735d3026520390a2a1d" + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/758ada29b5c80d933f906735d3026520390a2a1d", - "reference": "758ada29b5c80d933f906735d3026520390a2a1d", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", "shasum": "" }, "require": { - "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.10.26" + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-27T05:02:59+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.46", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "75dfe79a2aa30085b7132bb84377c24062193f33" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/75dfe79a2aa30085b7132bb84377c24062193f33", + "reference": "75dfe79a2aa30085b7132bb84377c24062193f33", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "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" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.46" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "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": "2025-12-06T08:01:15+00:00" + }, + { + "name": "rector/rector", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "f7166355dcf47482f27be59169b0825995f51c7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/f7166355dcf47482f27be59169b0825995f51c7d", + "reference": "f7166355dcf47482f27be59169b0825995f51c7d", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.1.33" }, "conflict": { "rector/rector-doctrine": "*", @@ -15366,6 +19007,9 @@ "rector/rector-phpunit": "*", "rector/rector-symfony": "*" }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, "bin": [ "bin/rector" ], @@ -15380,6 +19024,7 @@ "MIT" ], "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", "keywords": [ "automation", "dev", @@ -15388,7 +19033,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/0.18.0" + "source": "https://github.com/rectorphp/rector/tree/2.3.0" }, "funding": [ { @@ -15396,7 +19041,7 @@ "type": "github" } ], - "time": "2023-08-17T12:53:22+00:00" + "time": "2025-12-25T22:00:18+00:00" }, { "name": "roave/security-advisories", @@ -15404,33 +19049,48 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "9160fb2612003a99a28abbd588519d5ab3a77024" + "reference": "412d91dc6342787fd733523797d9c9faf2ad3ea5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/9160fb2612003a99a28abbd588519d5ab3a77024", - "reference": "9160fb2612003a99a28abbd588519d5ab3a77024", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/412d91dc6342787fd733523797d9c9faf2ad3ea5", + "reference": "412d91dc6342787fd733523797d9c9faf2ad3ea5", "shasum": "" }, "conflict": { "3f/pygmentize": "<1.2", - "admidio/admidio": "<4.2.11", - "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", + "adaptcms/adaptcms": "<=1.3", + "admidio/admidio": "<=4.3.16", + "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", + "aimeos/ai-client-html": ">=2020.04.1,<2020.10.27|>=2021.04.1,<2021.10.22|>=2022.04.1,<2022.10.13|>=2023.04.1,<2023.10.15|>=2024.04.1,<2024.04.7", + "aimeos/ai-cms-grapesjs": ">=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.9|>=2023.04.1,<2023.10.15|>=2024.04.1,<2024.10.8|>=2025.04.1,<2025.10.2", + "aimeos/ai-controller-frontend": "<2020.10.15|>=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.8|>=2023.04.1,<2023.10.9|==2024.04.1", + "aimeos/aimeos-core": ">=2022.04.1,<2022.10.17|>=2023.04.1,<2023.10.17|>=2024.04.1,<2024.04.7", "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", + "airesvsg/acf-to-rest-api": "<=3.1", "akaunting/akaunting": "<2.1.13", "akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53", - "alextselegidis/easyappointments": "<1.5", + "alextselegidis/easyappointments": "<1.5.2.0-beta1", + "alexusmai/laravel-file-manager": "<=3.3.1", + "alt-design/alt-redirect": "<1.6.4", + "altcha-org/altcha": "<1.3.1", "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", "amazing/media2click": ">=1,<1.3.3", + "ameos/ameos_tarteaucitron": "<1.2.23", "amphp/artax": "<1.0.6|>=2,<2.0.6", - "amphp/http": "<1.0.1", + "amphp/http": "<=1.7.2|>=2,<=2.1", "amphp/http-client": ">=4,<4.4", "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", @@ -15438,98 +19098,188 @@ "asymmetricrypt/asymmetricrypt": "<9.9.99", "athlon1600/php-proxy": "<=5.1", "athlon1600/php-proxy-app": "<=3", + "athlon1600/youtube-downloader": "<=4", "austintoddj/canvas": "<=3.4.2", - "automad/automad": "<1.8", + "auth0/auth0-php": ">=3.3,<8.18", + "auth0/login": "<7.20", + "auth0/symfony": "<=5.5", + "auth0/wordpress": "<=5.4", + "automad/automad": "<2.0.0.0-alpha5", + "automattic/jetpack": "<9.8", "awesome-support/awesome-support": "<=6.0.7", - "aws/aws-sdk-php": ">=3,<3.2.1", - "azuracast/azuracast": "<0.18.3", - "backdrop/backdrop": "<1.24.2", + "aws/aws-sdk-php": "<3.368", + "azuracast/azuracast": "<=0.23.1", + "b13/seo_basics": "<0.8.2", + "backdrop/backdrop": "<=1.32", "backpack/crud": "<3.4.9", - "badaso/core": "<2.7", - "bagisto/bagisto": "<0.1.5", + "backpack/filemanager": "<2.0.2|>=3,<3.0.9", + "bacula-web/bacula-web": "<9.7.1", + "badaso/core": "<=2.9.11", + "bagisto/bagisto": "<=2.3.7", "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": "<4.7.5", + "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": "<=2.9.2", + "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", "bolt/bolt": "<3.7.2", "bolt/core": "<=4.2", + "born05/craft-twofactorauthentication": "<3.3.4", "bottelet/flarepoint": "<2.2.1", + "bref/bref": "<2.1.17", "brightlocal/phpwhois": "<=4.2.5", "brotkrueml/codehighlight": "<2.7", "brotkrueml/schema": "<1.13.1|>=2,<2.5.1", "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", + "cadmium-org/cadmium-cms": "<=0.4.9", "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", "cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", "cardgate/magento2": "<2.0.33", "cardgate/woocommerce": "<=3.1.15", "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", + "cart2quote/module-quotation-encoded": ">=4.1.6,<=4.4.5|>=5,<5.4.4", "cartalyst/sentry": "<=2.1.6", "catfan/medoo": "<1.7.5", - "centreon/centreon": "<22.10.0.0-beta1", + "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", - "cockpit-hq/cockpit": "<=2.6.3", + "chrome-php/chrome": "<1.14", + "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", + "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", + "code16/sharp": "<9.11.1", "codeception/codeception": "<3.1.3|>=4,<4.1.22", - "codeigniter/framework": "<3.1.9", - "codeigniter4/framework": "<4.3.5", - "codeigniter4/shield": "<1.0.0.0-beta4", + "codeigniter/framework": "<3.1.10", + "codeigniter4/framework": "<4.6.2", + "codeigniter4/shield": "<1.0.0.0-beta8", "codiad/codiad": "<=2.8.4", - "composer/composer": "<1.10.26|>=2,<2.2.12|>=2.3,<2.3.5", - "concrete5/concrete5": "<9.2", + "codingms/additional-tca": ">=1.7,<1.15.17|>=1.16,<1.16.9", + "codingms/modules": "<4.3.11|>=5,<5.7.4|>=6,<6.4.2|>=7,<7.5.5", + "commerceteam/commerce": ">=0.9.6,<0.9.9", + "components/jquery": ">=1.0.3,<3.5", + "composer/composer": "<1.10.27|>=2,<2.2.26|>=2.3,<2.9.3", + "concrete5/concrete5": "<9.4.3", "concrete5/core": "<8.5.8|>=9,<9.1", "contao-components/mediaelement": ">=2.14.2,<2.21.1", - "contao/contao": ">=4,<4.4.56|>=4.5,<4.9.40|>=4.10,<4.11.7|>=4.13,<4.13.21|>=5.1,<5.1.4", - "contao/core": ">=2,<3.5.39", - "contao/core-bundle": "<4.9.42|>=4.10,<4.13.28|>=5,<5.1.10", - "contao/listing-bundle": ">=4,<4.4.8", + "contao/comments-bundle": ">=2,<4.13.40|>=5.0.0.0-RC1-dev,<5.3.4", + "contao/contao": ">=3,<3.5.37|>=4,<4.4.56|>=4.5,<4.13.56|>=5,<5.3.38|>=5.4.0.0-RC1-dev,<5.6.1", + "contao/core": "<3.5.39", + "contao/core-bundle": "<4.13.57|>=5,<5.3.42|>=5.4,<5.6.5", + "contao/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.4.14", - "croogo/croogo": "<4", + "couleurcitron/tarteaucitron-wp": "<0.3", + "craftcms/cms": "<=4.16.5|>=5,<=5.8.6", + "croogo/croogo": "<=4.0.7", "cuyz/valinor": "<0.12", + "czim/file-handling": "<1.5|>=2,<2.3", "czproject/git-php": "<4.0.3", + "damienharper/auditor-bundle": "<5.2.6", + "dapphp/securimage": "<3.6.6", "darylldoyle/safe-svg": "<1.9.10", "datadog/dd-trace": ">=0.30,<0.30.2", + "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", - "dcat/laravel-admin": "<=2.1.3.0-beta", + "dcat/laravel-admin": "<=2.1.3|==2.2.0.0-beta|==2.2.2.0-beta", "derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3", - "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1", + "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1|>=7,<7.4", "desperado/xml-bundle": "<=0.1.7", - "directmailteam/direct-mail": "<5.2.4", - "doctrine/annotations": ">=1,<1.2.7", + "dev-lancer/minecraft-motd-parser": "<=1.0.5", + "devcode-it/openstamanager": "<=2.9.4", + "devgroup/dotplant": "<2020.09.14-dev", + "digimix/wp-svg-upload": "<=1", + "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", + "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,<2.4.3|>=2.5,<2.5.1", + "doctrine/common": "<2.4.3|>=2.5,<2.5.1", "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2|>=3,<3.1.4", "doctrine/doctrine-bundle": "<1.5.2", - "doctrine/doctrine-module": "<=0.7.1", - "doctrine/mongodb-odm": ">=1,<1.0.2", - "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", - "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", - "dolibarr/dolibarr": "<17.0.1", - "dompdf/dompdf": "<2.0.2|==2.0.2", - "drupal/core": ">=7,<7.96|>=8,<9.4.14|>=9.5,<9.5.8|>=10,<10.0.8", - "drupal/drupal": ">=6,<6.38|>=7,<7.80|>=8,<8.9.16|>=9,<9.1.12|>=9.2,<9.2.4", + "doctrine/doctrine-module": "<0.7.2", + "doctrine/mongodb-odm": "<1.0.2", + "doctrine/mongodb-odm-bundle": "<3.0.1", + "doctrine/orm": ">=1,<1.2.4|>=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", + "dolibarr/dolibarr": "<21.0.3", + "dompdf/dompdf": "<2.0.4", + "doublethreedigital/guest-entries": "<3.1.2", + "drupal-pattern-lab/unified-twig-extensions": "<=0.1", + "drupal/access_code": "<2.0.5", + "drupal/acquia_dam": "<1.1.5", + "drupal/admin_audit_trail": "<1.0.5", + "drupal/ai": "<1.0.5", + "drupal/alogin": "<2.0.6", + "drupal/cache_utility": "<1.2.1", + "drupal/civictheme": "<1.12", + "drupal/commerce_alphabank_redirect": "<1.0.3", + "drupal/commerce_eurobank_redirect": "<2.1.1", + "drupal/config_split": "<1.10|>=2,<2.0.2", + "drupal/core": ">=6,<6.38|>=7,<7.102|>=8,<10.4.9|>=10.5,<10.5.6|>=11,<11.1.9|>=11.2,<11.2.8", + "drupal/core-recommended": ">=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", + "drupal/currency": "<3.5", + "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", + "drupal/email_tfa": "<2.0.6", + "drupal/formatter_suite": "<2.1", + "drupal/gdpr": "<3.0.1|>=3.1,<3.1.2", + "drupal/google_tag": "<1.8|>=2,<2.0.8", + "drupal/ignition": "<1.0.4", + "drupal/json_field": "<1.5", + "drupal/lightgallery": "<1.6", + "drupal/link_field_display_mode_formatter": "<1.6", + "drupal/matomo": "<1.24", + "drupal/oauth2_client": "<4.1.3", + "drupal/oauth2_server": "<2.1", + "drupal/obfuscate": "<2.0.1", + "drupal/plausible_tracking": "<1.0.2", + "drupal/quick_node_block": "<2", + "drupal/rapidoc_elements_field_formatter": "<1.0.1", + "drupal/reverse_proxy_header": "<1.1.2", + "drupal/simple_multistep": "<2", + "drupal/simple_oauth": ">=6,<6.0.7", + "drupal/spamspan": "<3.2.1", + "drupal/tfa": "<1.10", + "drupal/umami_analytics": "<1.0.1", + "duncanmcclean/guest-entries": "<3.1.2", "dweeves/magmi": "<=0.7.24", + "ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.1.2", "ecodev/newsletter": "<=4", "ectouch/ectouch": "<=2.7.2", + "egroupware/egroupware": "<23.1.20240624", "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", - "enshrined/svg-sanitize": "<0.15", + "enhavo/enhavo-app": "<=0.13.1", + "enshrined/svg-sanitize": "<0.22", "erusev/parsedown": "<1.7.2", "ether/logs": "<3.0.4", + "evolutioncms/evolution": "<=3.2.3", "exceedone/exment": "<4.4.3|>=5,<5.0.3", "exceedone/laravel-admin": "<2.2.3|==3", "ezsystems/demobundle": ">=5.4,<5.4.6.1-dev", @@ -15537,67 +19287,93 @@ "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1-dev", "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1-dev|>=5.4,<5.4.11.1-dev|>=2017.12,<2017.12.0.1-dev", "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", - "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.26", - "ezsystems/ezplatform-admin-ui-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-kernel": "<1.2.5.1-dev|>=1.3,<1.3.26", + "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", + "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.30", - "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.06,<=2019.03.5.1", + "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.31", + "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1", "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", - "ezyang/htmlpurifier": "<4.1.1", + "ezyang/htmlpurifier": "<=4.2", "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", - "facturascripts/facturascripts": "<=2022.08", + "facturascripts/facturascripts": "<=2025.4|==2025.11|==2025.41|==2025.43", + "fastly/magento2": "<1.2.26", "feehi/cms": "<=2.1.1", "feehi/feehicms": "<=2.1.1", "fenom/fenom": "<=2.12.1", + "filament/actions": ">=3.2,<3.2.123", + "filament/filament": ">=4,<4.3.1", + "filament/infolists": ">=3,<3.2.115", + "filament/tables": ">=3,<3.2.115", "filegator/filegator": "<7.8", + "filp/whoops": "<2.1.13", + "fineuploader/php-traditional-server": "<=1.2.2", "firebase/php-jwt": "<6", + "fisharebest/webtrees": "<=2.1.18", "fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2", - "fixpunkt/fp-newsletter": "<1.1.1|>=2,<2.1.2|>=2.2,<3.2.6", - "flarum/core": "<1.8", - "flarum/framework": "<1.8", + "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.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", + "floriangaerber/magnesium": "<0.3.1", "fluidtypo3/vhs": "<5.1.1", "fof/byobu": ">=0.3.0.0-beta2,<1.1.7", + "fof/pretty-mail": "<=1.1.2", "fof/upload": "<1.2.3", + "foodcoopshop/foodcoopshop": ">=3.2,<3.6.1", "fooman/tcpdf": "<6.2.22", "forkcms/forkcms": "<5.11.1", "fossar/tcpdf-parser": "<6.2.22", - "francoisjacquet/rosariosis": "<11", + "francoisjacquet/rosariosis": "<=11.5.1", "frappant/frp-form-answers": "<3.1.2|>=4,<4.0.2", "friendsofsymfony/oauth2-php": "<1.3", "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", - "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", + "friendsofsymfony/user-bundle": ">=1,<1.3.5", + "friendsofsymfony1/swiftmailer": ">=4,<5.4.13|>=6,<6.2.5", + "friendsofsymfony1/symfony1": ">=1.1,<1.5.19", "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", - "froala/wysiwyg-editor": "<3.2.7", - "froxlor/froxlor": "<2.1", + "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", + "froala/wysiwyg-editor": "<=4.3", + "froxlor/froxlor": "<=2.2.5", + "frozennode/administrator": "<=5.0.12", "fuel/core": "<1.8.1", - "funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3", + "funadmin/funadmin": "<=5.0.2", "gaoming13/wechat-php-sdk": "<=1.10.2", "genix/cms": "<=1.1.11", - "getgrav/grav": "<=1.7.42.1", - "getkirby/cms": "<3.5.8.3-dev|>=3.6,<3.6.6.3-dev|>=3.7,<3.7.5.2-dev|>=3.8,<3.8.4.1-dev|>=3.9,<3.9.6", - "getkirby/kirby": "<=2.5.12", + "georgringer/news": "<1.3.3", + "geshi/geshi": "<=1.0.9.1", + "getformwork/formwork": "<2.2", + "getgrav/grav": "<1.11.0.0-beta1", + "getkirby/cms": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1|>=5,<5.1.4", + "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.11.4", - "gleez/cms": "<=1.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", + "grumpydictator/firefly-iii": "<6.1.17", + "gugoan/economizzer": "<=0.9.0.0-beta1", "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", + "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", @@ -15607,275 +19383,424 @@ "hov/jobfair": "<1.0.13|>=2,<2.0.2", "httpsoft/http-message": "<1.0.12", "hyn/multi-tenant": ">=5.6,<5.7.2", - "ibexa/admin-ui": ">=4.2,<4.2.3", - "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3", + "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.25|>=5,<5.0.3", "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", - "ibexa/post-install": "<=1.0.4", - "ibexa/user": ">=4,<4.4.3", + "ibexa/http-cache": ">=4.6,<4.6.14", + "ibexa/post-install": "<1.0.16|>=4.6,<4.6.14", + "ibexa/solr": ">=4.5,<4.5.4", + "ibexa/user": ">=4,<4.4.3|>=5,<5.0.4", "icecoder/icecoder": "<=8.1", "idno/known": "<=1.3.1", - "illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10", - "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.99999|>=4.2,<=4.2.99999|>=5,<=5.0.99999|>=5.1,<=5.1.99999|>=5.2,<=5.2.99999|>=5.3,<=5.3.99999|>=5.4,<=5.4.99999|>=5.5,<=5.5.49|>=5.6,<=5.6.99999|>=5.7,<=5.7.99999|>=5.8,<=5.8.99999|>=6,<6.18.31|>=7,<7.22.4", + "ilicmiljan/secure-props": ">=1.2,<1.2.2", + "illuminate/auth": "<5.5.10", + "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<6.18.31|>=7,<7.22.4", "illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40", "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", + "imdbphp/imdbphp": "<=5.1.1", "impresscms/impresscms": "<=1.4.5", - "in2code/femanager": "<5.5.3|>=6,<6.3.4|>=7,<7.1", + "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.5.3|==13", "innologi/typo3-appointments": "<2.0.6", - "intelliants/subrion": "<=4.2.1", + "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.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|>=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/framework": ">=2.5.4,<=3.8.12", + "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": "<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.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", "khodakhah/nodcms": "<=3", - "kimai/kimai": "<1.1", + "kimai/kimai": "<=2.20.1", "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", "klaviyo/magento2-extension": ">=1,<3", - "knplabs/knp-snappy": "<1.4.2", + "knplabs/knp-snappy": "<=1.4.2", "kohana/core": "<3.3.3", - "krayin/laravel-crm": "<1.2.2", + "koillection/koillection": "<1.6.12", + "krayin/laravel-crm": "<=1.3", "kreait/firebase-php": ">=3.2,<3.8.1", + "kumbiaphp/kumbiapp": "<=1.1.1", "la-haute-societe/tcpdf": "<6.2.22", "laminas/laminas-diactoros": "<2.18.1|==2.19|==2.20|==2.21|==2.22|==2.23|>=2.24,<2.24.2|>=2.25,<2.25.2", "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", "laminas/laminas-http": "<2.14.2", + "lara-zeus/artemis": ">=1,<=1.0.6", + "lara-zeus/dynamic-dashboard": ">=3,<=3.0.1", "laravel/fortify": "<1.11.1", - "laravel/framework": "<6.20.44|>=7,<7.30.6|>=8,<8.75", - "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", + "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", + "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": "<0.18.3", + "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", - "librenms/librenms": "<2017.08.18", + "libreform/libreform": ">=2,<=2.0.8", + "librenms/librenms": "<25.12", "liftkit/database": "<2.13.2", - "limesurvey/limesurvey": "<3.27.19", + "lightsaml/lightsaml": "<1.3.5", + "limesurvey/limesurvey": "<6.5.12", "livehelperchat/livehelperchat": "<=3.91", - "livewire/livewire": ">2.2.4,<2.2.6", + "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", - "magento/community-edition": "<=2.4", + "macropay-solutions/laravel-crud-wizard-free": "<3.4.17", + "maestroerror/php-heic-to-jpg": "<1.0.5", + "magento/community-edition": "<2.4.6.0-patch13|>=2.4.7.0-beta1,<2.4.7.0-patch8|>=2.4.8.0-beta1,<2.4.8.0-patch3|>=2.4.9.0-alpha1,<2.4.9.0-alpha3|==2.4.9", + "magento/core": "<=1.9.4.5", "magento/magento1ce": "<1.9.4.3-dev", "magento/magento1ee": ">=1,<1.14.4.3-dev", - "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2.0-patch2", + "magento/product-community-edition": "<2.4.4.0-patch9|>=2.4.5,<2.4.5.0-patch8|>=2.4.6,<2.4.6.0-patch6|>=2.4.7,<2.4.7.0-patch1", + "magento/project-community-edition": "<=2.0.2", + "magneto/core": "<1.9.4.4-dev", + "mahocommerce/maho": "<25.9", "maikuolan/phpmussel": ">=1,<1.6", - "mantisbt/mantisbt": "<=2.25.5", + "mainwp/mainwp": "<=4.4.3.3", + "manogi/nova-tiptap": "<=3.2.6", + "mantisbt/mantisbt": "<2.27.2", "marcwillmann/turn": "<0.3.3", + "marshmallow/nova-tiptap": "<5.7", + "matomo/matomo": "<1.11", "matyhtf/framework": "<3.0.6", - "mautic/core": "<4.3", - "mediawiki/core": ">=1.27,<1.27.6|>=1.29,<1.29.3|>=1.30,<1.30.2|>=1.31,<1.31.9|>=1.32,<1.32.6|>=1.32.99,<1.33.3|>=1.33.99,<1.34.3|>=1.34.99,<1.35", + "mautic/core": "<5.2.9|>=6,<6.0.7", + "mautic/core-lib": ">=1.0.0.0-beta,<4.4.13|>=5.0.0.0-alpha,<5.1.1", + "mautic/grapes-js-builder-bundle": ">=4,<4.4.18|>=5,<5.2.9|>=6,<6.0.7", + "maximebf/debugbar": "<1.19", + "mdanter/ecc": "<2", + "mediawiki/abuse-filter": "<1.39.9|>=1.40,<1.41.3|>=1.42,<1.42.2", + "mediawiki/cargo": "<3.8.3", + "mediawiki/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", - "microweber/microweber": "<=1.3.4", + "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.19", + "mikehaertl/php-shellcommand": "<1.6.1", + "mineadmin/mineadmin": "<=3.0.9", "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", + "mongodb/mongodb-extension": "<1.21.2", "monolog/monolog": ">=1.8,<1.12", - "moodle/moodle": "<4.2.0.0-RC2-dev|==4.2", + "moodle/moodle": "<4.4.11|>=4.5.0.0-beta,<4.5.7|>=5.0.0.0-beta,<5.0.3", + "moonshine/moonshine": "<=3.12.5", + "mos/cimage": "<0.7.19", "movim/moxl": ">=0.8,<=0.10", + "movingbytes/social-network": "<=1.2.1", "mpdf/mpdf": "<=7.1.7", + "munkireport/comment": "<4", + "munkireport/managedinstalls": "<2.6", + "munkireport/munki_facts": "<1.5", + "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", "neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", "neos/form": ">=1.2,<4.3.3|>=5,<5.0.9|>=5.1,<5.1.3", - "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.9.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2", - "neos/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", + "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.0.10", + "neuron-core/neuron-ai": "<=2.8.11", + "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", + "nzedb/nzedb": "<0.8", "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", "october/backend": "<1.1.2", "october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1", - "october/october": "<=3.4.4", + "october/october": "<3.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.0.66", - "onelogin/php-saml": "<2.10.4", - "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5", - "open-web-analytics/open-web-analytics": "<1.7.4", - "opencart/opencart": "<=3.0.3.7", + "october/system": "<3.7.5", + "oliverklee/phpunit": "<3.5.15", + "omeka/omeka-s": "<4.0.3", + "onelogin/php-saml": "<2.21.1|>=3,<3.8.1|>=4,<4.3.1", + "oneup/uploader-bundle": ">=1,<1.9.3|>=2,<2.1.5", + "open-web-analytics/open-web-analytics": "<1.8.1", + "opencart/opencart": ">=0", "openid/php-openid": "<2.3", - "openmage/magento-lts": "<19.4.22|>=20,<20.0.19", - "opensource-workshop/connect-cms": "<1.7.2|>=2,<2.3.2", - "orchid/platform": ">=9,<9.4.4|>=14.0.0.0-alpha4,<14.5", - "oro/commerce": ">=4.1,<5.0.6", + "openmage/magento-lts": "<20.16", + "opensolutions/vimbadmin": "<=3.0.15", + "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", "oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7", - "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<4.2.8", - "oxid-esales/oxideshop-ce": "<4.5", + "oro/crm-call-bundle": ">=4.2,<=4.2.5|>=5,<5.0.4|>=5.1,<5.1.1", + "oro/customer-portal": ">=4.1,<=4.1.13|>=4.2,<=4.2.10|>=5,<=5.0.11|>=5.1,<=5.1.3", + "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<=4.2.10|>=5,<=5.0.12|>=5.1,<=5.1.3", + "oveleon/contao-cookiebar": "<1.16.3|>=2,<2.1.3", + "oxid-esales/oxideshop-ce": "<=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", "pagarme/pagarme-php": "<3", "pagekit/pagekit": "<=1.0.18", + "paragonie/ecc": "<2.0.1", "paragonie/random_compat": "<2", - "passbolt/passbolt_api": "<2.11", + "paragonie/sodium_compat": "<1.24|>=2,<2.5", + "passbolt/passbolt_api": "<4.6.2", + "paypal/adaptivepayments-sdk-php": "<=3.9.2", + "paypal/invoice-sdk-php": "<=3.9", "paypal/merchant-sdk-php": "<3.12", + "paypal/permissions-sdk-php": "<=3.9.1", "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", "phanan/koel": "<5.1.4", + "phenx/php-svg-lib": "<0.5.2", + "php-censor/php-censor": "<2.0.13|>=2.1,<2.1.5", "php-mod/curl": "<2.3.2", - "phpbb/phpbb": "<3.2.10|>=3.3,<3.3.1", + "phpbb/phpbb": "<3.3.11", + "phpems/phpems": ">=6,<=6.1.3", "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", "phpmailer/phpmailer": "<6.5", "phpmussel/phpmussel": ">=1,<1.6", - "phpmyadmin/phpmyadmin": "<5.2.1", - "phpmyfaq/phpmyfaq": "<=3.1.7", - "phpoffice/phpexcel": "<1.8", - "phpoffice/phpspreadsheet": "<1.16", - "phpseclib/phpseclib": "<2.0.31|>=3,<3.0.19", + "phpmyadmin/phpmyadmin": "<5.2.2", + "phpmyfaq/phpmyfaq": "<=4.0.13", + "phpoffice/common": "<0.2.9", + "phpoffice/math": "<=0.2", + "phpoffice/phpexcel": "<=1.8.2", + "phpoffice/phpspreadsheet": "<1.30|>=2,<2.1.12|>=2.2,<2.4|>=3,<3.10|>=4,<5", + "phppgadmin/phppgadmin": "<=7.13", + "phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36", "phpservermon/phpservermon": "<3.6", - "phpsysinfo/phpsysinfo": "<3.2.5", - "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5,<5.6.3", + "phpsysinfo/phpsysinfo": "<3.4.3", + "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3", "phpwhois/phpwhois": "<=4.2.5", "phpxmlrpc/extras": "<0.6.1", "phpxmlrpc/phpxmlrpc": "<4.9.2", "pi/pi": "<=2.5", - "pimcore/admin-ui-classic-bundle": "<1.0.3", - "pimcore/customer-management-framework-bundle": "<3.4.2", + "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": "<10.6.8", - "pixelfed/pixelfed": "<=0.11.4", + "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": "<4.22.3|>=5,<5.2.1", + "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", + "prestashop/blockreassurance": "<=5.1.3", "prestashop/blockwishlist": ">=2,<2.1.1", "prestashop/contactform": ">=1.0.1,<4.3", "prestashop/gamification": "<2.3.2", - "prestashop/prestashop": "<=8.1", + "prestashop/prestashop": "<8.2.3", "prestashop/productcomments": "<5.0.2", + "prestashop/ps_checkout": "<4.4.1|>=5,<5.0.5", + "prestashop/ps_contactinfo": "<=3.3.2", "prestashop/ps_emailsubscription": "<2.6.1", "prestashop/ps_facetedsearch": "<3.4.1", "prestashop/ps_linklist": "<3.1", - "privatebin/privatebin": "<1.4", - "processwire/processwire": "<=3.0.200", + "privatebin/privatebin": "<1.4|>=1.5,<1.7.4|>=1.7.7,<2.0.3", + "processwire/processwire": "<=3.0.246", "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", "propel/propel1": ">=1,<=1.7.1", - "pterodactyl/panel": "<1.7", - "ptheofan/yii2-statemachine": ">=2", + "pterodactyl/panel": "<1.12", + "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", "pyrocms/pyrocms": "<=3.9.1", + "qcubed/qcubed": "<=3.1.1", + "quickapps/cms": "<=2.0.0.0-beta2", + "rainlab/blog-plugin": "<1.4.1", "rainlab/debugbar-plugin": "<3.1", "rainlab/user-plugin": "<=1.4.5", "rankmath/seo-by-rank-math": "<=1.0.95", "rap2hpoutre/laravel-log-viewer": "<0.13", "react/http": ">=0.7,<1.9", "really-simple-plugins/complianz-gdpr": "<6.4.2", - "remdex/livehelperchat": "<3.99", + "redaxo/source": "<5.20.1", + "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": "<3.0.4", + "robrichards/xmlseclibs": "<=3.1.3", "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.7.11|>=1.8,<1.8.9", + "sabre/dav": ">=1.6,<1.7.11|>=1.8,<1.8.9", + "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", - "shopware/core": "<=6.4.20", - "shopware/platform": "<=6.4.20", + "sheng/yiicms": "<1.2.1", + "shopware/core": "<6.6.10.9-dev|>=6.7,<6.7.4.1-dev", + "shopware/platform": "<6.6.10.7-dev|>=6.7,<6.7.3.1-dev", "shopware/production": "<=6.3.5.2", - "shopware/shopware": "<=5.7.17", - "shopware/storefront": "<=6.4.8.1", - "shopxo/shopxo": "<2.2.6", + "shopware/shopware": "<=5.7.17|>=6.4.6,<6.6.10.10-dev|>=6.7,<6.7.5.1-dev", + "shopware/storefront": "<6.6.10.10-dev|>=6.7,<6.7.5.1-dev", + "shopxo/shopxo": "<=6.4", "showdoc/showdoc": "<2.10.4", + "shuchkin/simplexlsx": ">=1.0.12,<1.1.13", "silverstripe-australia/advancedreports": ">=1,<=2", - "silverstripe/admin": "<1.13.6", + "silverstripe/admin": "<1.13.19|>=2,<2.1.8", "silverstripe/assets": ">=1,<1.11.1", "silverstripe/cms": "<4.11.3", - "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1", + "silverstripe/comments": ">=1.3,<3.1.1", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": "<4.13.14|>=5,<5.0.13", - "silverstripe/graphql": "<3.5.2|>=4.0.0.0-alpha1,<4.0.0.0-alpha2|>=4.1.1,<4.1.2|>=4.2.2,<4.2.3", + "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", "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", - "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", + "silverstripe/reports": "<5.2.3", + "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4|>=2.1,<2.1.2", "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", "silverstripe/subsites": ">=2,<2.6.1", "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", - "silverstripe/userforms": "<3", + "silverstripe/userforms": "<3|>=5,<5.4.2", "silverstripe/versioned-admin": ">=1,<1.11.1", + "simogeo/filemanager": "<=2.5", "simple-updates/phpwhois": "<=1", - "simplesamlphp/saml2": "<1.15.4|>=2,<2.3.8|>=3,<3.1.4", + "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", "simplesamlphp/simplesamlphp-module-openidprovider": "<0.9", + "simplesamlphp/xml-common": "<1.20", + "simplesamlphp/xml-security": "==1.6.11", "simplito/elliptic-php": "<1.0.6", "sitegeist/fluid-components": "<3.5", - "sjbr/sr-freecap": "<=2.5.2", + "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": "<3.1.48|>=4,<4.3.1", - "snipe/snipe-it": "<=6.0.14", + "smarty/smarty": "<4.5.3|>=5,<5.1.1", + "snipe/snipe-it": "<=8.3.4", "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", - "spatie/browsershot": "<3.57.4", - "spipu/html2pdf": "<5.2.4", + "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", + "spiral/roadrunner": "<2025.1", "spoon/library": "<1.4.1", "spoonity/tcpdf": "<6.2.22", "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", - "ssddanbrown/bookstack": "<22.02.3", - "statamic/cms": "<4.10", + "ssddanbrown/bookstack": "<24.05.1", + "starcitizentools/citizen-skin": ">=1.9.4,<3.9", + "starcitizentools/short-description": ">=4,<4.0.1", + "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2|>=3,<3.1.1", + "starcitizenwiki/embedvideo": "<=4", + "statamic/cms": "<=5.22", "stormpath/sdk": "<9.9.99", - "studio-42/elfinder": "<2.1.62", + "studio-42/elfinder": "<=2.1.64", + "studiomitte/friendlycaptcha": "<0.1.4", "subhh/libconnect": "<7.0.8|>=8,<8.1", - "subrion/cms": "<=4.2.1", "sukohi/surpass": "<1", - "sulu/sulu": "<1.6.44|>=2,<2.2.18|>=2.3,<2.3.8|==2.4.0.0-RC1|>=2.5,<2.5.10", + "sulu/form-bundle": ">=2,<2.5.3", + "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": ">=4,<5.4.5", + "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/resource-bundle": "<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", - "sylius/sylius": "<1.9.10|>=1.10,<1.10.11|>=1.11,<1.11.2", - "symbiote/silverstripe-multivaluefield": ">=3,<3.0.99", + "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", "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4", "symbiote/silverstripe-seed": "<6.0.3", "symbiote/silverstripe-versionedfiles": "<=2.0.3", @@ -15884,8 +19809,9 @@ "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", - "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<=5.3.14|>=5.4.3,<=5.4.3|>=6.0.3,<=6.0.3", - "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", + "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4", + "symfony/http-client": ">=4.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", + "symfony/http-foundation": "<5.4.50|>=6,<6.4.29|>=7,<7.3.7", "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", @@ -15893,109 +19819,167 @@ "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/polyfill": ">=1,<1.10", "symfony/polyfill-php55": ">=1,<1.10", + "symfony/process": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/routing": ">=2,<2.0.19", + "symfony/runtime": ">=5.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", - "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.4.10|>=7,<7.0.10|>=7.1,<7.1.3", "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", - "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.3.2", + "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", - "symfony/symfony": "<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/symfony": "<5.4.50|>=6,<6.4.29|>=7,<7.3.7", "symfony/translation": ">=2,<2.0.17", - "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", + "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", - "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", + "symfony/webhook": ">=6.3,<6.3.8", + "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7|>=2.2.0.0-beta1,<2.2.0.0-beta2", + "symphonycms/symphony-2": "<2.6.4", "t3/dce": "<0.11.5|>=2.2,<2.6.2", "t3g/svg-sanitizer": "<1.0.3", - "tastyigniter/tastyigniter": "<3.3", - "tcg/voyager": "<=1.4", - "tecnickcom/tcpdf": "<6.2.22", + "t3s/content-consent": "<1.0.3|>=2,<2.0.2", + "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", "thelia/backoffice-default-template": ">=2.1,<2.1.2", "thelia/thelia": ">=2.1,<2.1.3", "theonedemon/phpwhois": "<=4.2.5", - "thinkcmf/thinkcmf": "<=5.1.7", - "thorsten/phpmyfaq": "<3.2.0.0-beta2", + "thinkcmf/thinkcmf": "<6.0.8", + "thorsten/phpmyfaq": "<4.0.16|>=4.1.0.0-alpha,<=4.1.0.0-beta2", "tikiwiki/tiki-manager": "<=17.1", - "tinymce/tinymce": "<5.10.7|>=6,<6.3.1", + "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", + "tinymce/tinymce": "<7.2", "tinymighty/wiki-seo": "<1.2.2", "titon/framework": "<9.9.99", + "tltneon/lgsl": "<7", "tobiasbg/tablepress": "<=2.0.0.0-RC1", - "topthink/framework": "<6.0.14", + "topthink/framework": "<6.0.17|>=6.1,<=8.0.4", "topthink/think": "<=6.1.1", - "topthink/thinkphp": "<=3.2.3", + "topthink/thinkphp": "<=3.2.3|>=6.1.3,<=8.0.4", + "torrentpier/torrentpier": "<=2.8.8", "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", - "tribalsystems/zenario": "<=9.3.57595", + "tribalsystems/zenario": "<=9.7.61188", "truckersmp/phpwhois": "<=4.3.1", "ttskch/pagination-service-provider": "<1", - "twig/twig": "<1.44.7|>=2,<2.15.3|>=3,<3.4.3", - "typo3/cms": "<8.7.38|>=9,<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", - "typo3/cms-backend": ">=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", - "typo3/cms-core": "<8.7.51|>=9,<9.5.42|>=10,<10.4.39|>=11,<11.5.30|>=12,<12.4.4", + "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.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-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.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.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-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.1|>=2,<2.1.2", + "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", "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", "typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", "typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10", "ua-parser/uap-php": "<3.8", - "unisharp/laravel-filemanager": "<=2.5.1", + "uasoft-indonesia/badaso": "<=2.9.7", + "unisharp/laravel-filemanager": "<2.9.1", + "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", - "verot/class.upload.php": "<=1.0.3|>=2,<=2.0.4", + "verbb/comments": "<1.5.5", + "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.2", + "wallabag/wallabag": "<2.6.11", "wanglelecc/laracms": "<=1.0.3", - "web-auth/webauthn-framework": ">=3.3,<3.3.4", + "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", - "wintercms/winter": "<1.2.3", - "woocommerce/woocommerce": "<6.6", - "wp-cli/wp-cli": "<2.5", + "winter/wn-backend-module": "<1.2.4", + "winter/wn-cms-module": "<1.0.476|>=1.1,<1.1.11|>=1.2,<1.2.7", + "winter/wn-dusk-plugin": "<2.1", + "winter/wn-system-module": "<1.2.4", + "wintercms/winter": "<=1.2.3", + "wireui/wireui": "<1.19.3|>=2,<2.1.3", + "woocommerce/woocommerce": "<6.6|>=8.8,<8.8.5|>=8.9,<8.9.3", + "wp-cli/wp-cli": ">=0.12,<2.5", "wp-graphql/wp-graphql": "<=1.14.5", + "wp-premium/gravityforms": "<2.4.21", "wpanel/wpanel4-cms": "<=4.3.1", "wpcloud/wp-stateless": "<3.2", - "wwbn/avideo": "<=12.4", + "wpglobus/wpglobus": "<=1.9.6", + "wwbn/avideo": "<14.3", "xataface/xataface": "<3", "xpressengine/xpressengine": "<3.0.15", - "yeswiki/yeswiki": "<4.1", - "yetiforce/yetiforce-crm": "<=6.4", + "yab/quarx": "<2.4.5", + "yeswiki/yeswiki": "<=4.5.4", + "yetiforce/yetiforce-crm": "<6.5", "yidashi/yii2cmf": "<=2", "yii2mod/yii2-cms": "<1.9.2", - "yiisoft/yii": "<1.1.27", - "yiisoft/yii2": "<2.0.38", + "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", + "yourls/yourls": "<=1.10.2", + "yuan1994/tpadmin": "<=1.3.12", + "yungifez/skuul": "<=2.6.5", + "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", "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", - "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5", + "zendframework/zend-db": "<2.2.10|>=2.3,<2.3.5", "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3", "zendframework/zend-diactoros": "<1.8.4", "zendframework/zend-feed": "<2.10.3", @@ -16003,22 +19987,30 @@ "zendframework/zend-http": "<2.8.1", "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", - "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2", + "zendframework/zend-mail": "<2.4.11|>=2.5,<2.7.2", "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4", + "zendframework/zend-session": ">=2,<2.2.9|>=2.3,<2.3.4", "zendframework/zend-validator": ">=2.3,<2.3.6", "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", "zendframework/zendframework": "<=3", "zendframework/zendframework1": "<1.12.20", - "zendframework/zendopenid": ">=2,<2.0.2", - "zendframework/zendxml": "<1.0.1", + "zendframework/zendopenid": "<2.0.2", + "zendframework/zendrest": "<2.0.2", + "zendframework/zendservice-amazon": "<2.0.3", + "zendframework/zendservice-api": "<1", + "zendframework/zendservice-audioscrobbler": "<2.0.2", + "zendframework/zendservice-nirvanix": "<2.0.2", + "zendframework/zendservice-slideshare": "<2.0.2", + "zendframework/zendservice-technorati": "<2.0.2", + "zendframework/zendservice-windowsazure": "<2.0.2", + "zendframework/zendxml": ">=1,<1.0.1", "zenstruck/collection": "<0.2.1", "zetacomponents/mail": "<1.8.2", "zf-commons/zfc-user": "<1.2.2", "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", "zfr/zfr-oauth2-server-module": "<0.1.2", - "zoujingli/thinkadmin": "<6.0.22" + "zoujingli/thinkadmin": "<=6.1.53" }, "default-branch": true, "type": "metapackage", @@ -16056,33 +20048,353 @@ "type": "tidelift" } ], - "time": "2023-08-23T09:04:12+00:00" + "time": "2025-12-30T23:05:26+00:00" }, { - "name": "sebastian/diff", - "version": "5.0.3", + "name": "sebastian/cli-parser", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/912dc2fbe3e3c1e7873313cc801b100b6c68c87b", - "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0", + "phpunit/phpunit": "^11.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", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5" + }, + "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", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-19T07:56:08+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "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": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "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": "2025-08-10T08:07:46+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -16115,7 +20427,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" }, "funding": [ { @@ -16123,94 +20435,683 @@ "type": "github" } ], - "time": "2023-05-01T07:48:21+00:00" + "time": "2024-07-03T04:53:05+00:00" }, { - "name": "spatie/array-to-xml", - "version": "3.2.0", + "name": "sebastian/environment", + "version": "7.2.1", "source": { "type": "git", - "url": "https://github.com/spatie/array-to-xml.git", - "reference": "f9ab39c808500c347d5a8b6b13310bd5221e39e7" + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/f9ab39c808500c347d5a8b6b13310bd5221e39e7", - "reference": "f9ab39c808500c347d5a8b6b13310bd5221e39e7", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", "shasum": "" }, "require": { - "ext-dom": "*", - "php": "^8.0" + "php": ">=8.2" }, "require-dev": { - "mockery/mockery": "^1.2", - "pestphp/pest": "^1.21", - "spatie/pest-plugin-snapshots": "^1.1" + "phpunit/phpunit": "^11.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "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": "2025-05-21T11:55:47+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "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": "2025-09-24T06:12:51+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.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", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "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" + }, + { + "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/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:42:22+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "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": "2025-08-09T06:55:48+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "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": { - "Spatie\\ArrayToXml\\": "src" - } + "classmap": [ + "lib/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Freek Van der Herten", - "email": "freek@spatie.be", - "homepage": "https://freek.dev", - "role": "Developer" - } - ], - "description": "Convert an array to xml", - "homepage": "https://github.com/spatie/array-to-xml", + "description": "A static analysis tool to detect side effects in PHP code", "keywords": [ - "array", - "convert", - "xml" + "static analysis" ], "support": { - "source": "https://github.com/spatie/array-to-xml/tree/3.2.0" + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" }, "funding": [ { - "url": "https://spatie.be/open-source/support-us", - "type": "custom" - }, - { - "url": "https://github.com/spatie", + "url": "https://github.com/staabm", "type": "github" } ], - "time": "2023-07-19T18:30:26+00:00" + "time": "2024-10-20T05:08:20+00:00" }, { "name": "symfony/browser-kit", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "ca4a988488f61ac18f8f845445eabdd36f89aa8d" + "reference": "d5b5c731005f224fbc25289587a8538e4f62c762" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/ca4a988488f61ac18f8f845445eabdd36f89aa8d", - "reference": "ca4a988488f61ac18f8f845445eabdd36f89aa8d", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/d5b5c731005f224fbc25289587a8538e4f62c762", + "reference": "d5b5c731005f224fbc25289587a8538e4f62c762", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/dom-crawler": "^5.4|^6.0" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/dom-crawler": "^6.4|^7.0|^8.0" }, "require-dev": { - "symfony/css-selector": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/mime": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0" + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -16238,7 +21139,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v6.3.2" + "source": "https://github.com/symfony/browser-kit/tree/v7.4.3" }, "funding": [ { @@ -16249,42 +21150,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": "2023-07-06T06:56:43+00:00" + "time": "2025-12-16T08:02:06+00:00" }, { "name": "symfony/debug-bundle", - "version": "v6.3.2", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/debug-bundle.git", - "reference": "3f04a578e1a9f1d7da84a87b690c03123e5d8c31" + "reference": "329383fb895353e3c8ab792cc35c4a7e7b17881b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/3f04a578e1a9f1d7da84a87b690c03123e5d8c31", - "reference": "3f04a578e1a9f1d7da84a87b690c03123e5d8c31", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/329383fb895353e3c8ab792cc35c4a7e7b17881b", + "reference": "329383fb895353e3c8ab792cc35c4a7e7b17881b", "shasum": "" }, "require": { + "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=8.1", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/twig-bridge": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "conflict": { - "symfony/config": "<5.4", - "symfony/dependency-injection": "<5.4" + "php": ">=8.2", + "symfony/config": "^7.3|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/twig-bridge": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "require-dev": { - "symfony/config": "^5.4|^6.0", - "symfony/web-profiler-bundle": "^5.4|^6.0" + "symfony/web-profiler-bundle": "^6.4|^7.0|^8.0" }, "type": "symfony-bundle", "autoload": { @@ -16312,7 +21214,7 @@ "description": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/debug-bundle/tree/v6.3.2" + "source": "https://github.com/symfony/debug-bundle/tree/v7.4.0" }, "funding": [ { @@ -16324,70 +21226,7 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-07-13T14:29:38+00:00" - }, - { - "name": "symfony/dom-crawler", - "version": "v6.3.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "8aa333f41f05afc7fc285a976b58272fd90fc212" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/8aa333f41f05afc7fc285a976b58272fd90fc212", - "reference": "8aa333f41f05afc7fc285a976b58272fd90fc212", - "shasum": "" - }, - "require": { - "masterminds/html5": "^2.6", - "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0" - }, - "require-dev": { - "symfony/css-selector": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "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": "Eases DOM navigation for HTML and XML documents", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v6.3.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -16395,56 +21234,56 @@ "type": "tidelift" } ], - "time": "2023-06-05T15:30:22+00:00" + "time": "2025-10-24T13:56:35+00:00" }, { "name": "symfony/maker-bundle", - "version": "v1.50.0", + "version": "v1.65.1", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "a1733f849b999460c308e66f6392fb09b621fa86" + "reference": "eba30452d212769c9a5bcf0716959fd8ba1e54e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/a1733f849b999460c308e66f6392fb09b621fa86", - "reference": "a1733f849b999460c308e66f6392fb09b621fa86", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/eba30452d212769c9a5bcf0716959fd8ba1e54e3", + "reference": "eba30452d212769c9a5bcf0716959fd8ba1e54e3", "shasum": "" }, "require": { "doctrine/inflector": "^2.0", - "nikic/php-parser": "^4.11", - "php": ">=8.0", - "symfony/config": "^5.4.7|^6.0", - "symfony/console": "^5.4.7|^6.0", - "symfony/dependency-injection": "^5.4.7|^6.0", + "nikic/php-parser": "^5.0", + "php": ">=8.1", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/deprecation-contracts": "^2.2|^3", - "symfony/filesystem": "^5.4.7|^6.0", - "symfony/finder": "^5.4.3|^6.0", - "symfony/framework-bundle": "^5.4.7|^6.0", - "symfony/http-kernel": "^5.4.7|^6.0", - "symfony/process": "^5.4.7|^6.0" + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0" }, "conflict": { - "doctrine/doctrine-bundle": "<2.4", - "doctrine/orm": "<2.10", - "symfony/doctrine-bridge": "<5.4" + "doctrine/doctrine-bundle": "<2.10", + "doctrine/orm": "<2.15" }, "require-dev": { "composer/semver": "^3.0", - "doctrine/doctrine-bundle": "^2.4", - "doctrine/orm": "^2.10.0", - "symfony/http-client": "^5.4.7|^6.0", - "symfony/phpunit-bridge": "^5.4.17|^6.0", - "symfony/polyfill-php80": "^1.16.0", - "symfony/security-core": "^5.4.7|^6.0", - "symfony/yaml": "^5.4.3|^6.0", - "twig/twig": "^2.0|^3.0" + "doctrine/doctrine-bundle": "^2.5.0|^3.0.0", + "doctrine/orm": "^2.15|^3", + "doctrine/persistence": "^3.1|^4.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/phpunit-bridge": "^6.4.1|^7.0|^8.0", + "symfony/security-core": "^6.4|^7.0|^8.0", + "symfony/security-http": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0", + "twig/twig": "^3.0|^4.x-dev" }, "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-main": "1.0-dev" + "dev-main": "1.x-dev" } }, "autoload": { @@ -16473,7 +21312,7 @@ ], "support": { "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.50.0" + "source": "https://github.com/symfony/maker-bundle/tree/v1.65.1" }, "funding": [ { @@ -16484,37 +21323,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": "2023-07-10T18:21:57+00:00" + "time": "2025-12-02T07:14:37+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "e020e1efbd1b42cb670fcd7d19a25abbddba035d" + "reference": "f933e68bb9df29d08077a37e1515a23fea8562ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/e020e1efbd1b42cb670fcd7d19a25abbddba035d", - "reference": "e020e1efbd1b42cb670fcd7d19a25abbddba035d", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/f933e68bb9df29d08077a37e1515a23fea8562ab", + "reference": "f933e68bb9df29d08077a37e1515a23fea8562ab", "shasum": "" }, "require": { - "php": ">=7.1.3" - }, - "conflict": { - "phpunit/phpunit": "<7.5|9.1.2" + "php": ">=8.1.0" }, "require-dev": { - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/error-handler": "^5.4|^6.0", - "symfony/polyfill-php81": "^1.27" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4.3|^7.0.3|^8.0" }, "bin": [ "bin/simple-phpunit" @@ -16522,8 +21361,8 @@ "type": "symfony-bridge", "extra": { "thanks": { - "name": "phpunit/phpunit", - "url": "https://github.com/sebastianbergmann/phpunit" + "url": "https://github.com/sebastianbergmann/phpunit", + "name": "phpunit/phpunit" } }, "autoload": { @@ -16534,7 +21373,8 @@ "Symfony\\Bridge\\PhpUnit\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Tests/", + "/bin/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -16553,8 +21393,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.3.2" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.4.3" }, "funding": [ { @@ -16565,46 +21408,55 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-07-12T16:00:22+00:00" + "time": "2025-12-09T15:33:45+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v6.3.2", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "6101b5ab7857c373d237e121f9060c68b32e1373" + "reference": "5220b59d06f6554658a0dc4d6bd4497a789e51dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/6101b5ab7857c373d237e121f9060c68b32e1373", - "reference": "6101b5ab7857c373d237e121f9060c68b32e1373", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/5220b59d06f6554658a0dc4d6bd4497a789e51dd", + "reference": "5220b59d06f6554658a0dc4d6bd4497a789e51dd", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/config": "^5.4|^6.0", - "symfony/framework-bundle": "^5.4|^6.0", - "symfony/http-kernel": "^6.3", - "symfony/routing": "^5.4|^6.0", - "symfony/twig-bundle": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" + "composer-runtime-api": ">=2.1", + "php": ">=8.2", + "symfony/config": "^7.3|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", + "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/twig-bundle": "^6.4|^7.0|^8.0", + "twig/twig": "^3.15" }, "conflict": { - "symfony/form": "<5.4", - "symfony/mailer": "<5.4", - "symfony/messenger": "<5.4" + "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", - "symfony/console": "^5.4|^6.0", - "symfony/css-selector": "^5.4|^6.0", - "symfony/stopwatch": "^5.4|^6.0" + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/runtime": "^6.4.13|^7.1.6|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "symfony-bundle", "autoload": { @@ -16635,7 +21487,7 @@ "dev" ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v6.3.2" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.4.3" }, "funding": [ { @@ -16646,199 +21498,77 @@ "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": "2023-07-19T20:17:28+00:00" + "time": "2025-12-27T17:05:22+00:00" }, { - "name": "symplify/easy-coding-standard", - "version": "12.0.6", + "name": "theseer/tokenizer", + "version": "1.3.1", "source": { "type": "git", - "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", - "reference": "7372622f5d9126ec50b0923d7eb6b778fac575a5" + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/7372622f5d9126ec50b0923d7eb6b778fac575a5", - "reference": "7372622f5d9126ec50b0923d7eb6b778fac575a5", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { - "php": ">=7.2" + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" }, - "conflict": { - "friendsofphp/php-cs-fixer": "<3.0", - "squizlabs/php_codesniffer": "<3.6", - "symplify/coding-standard": "<11.3" - }, - "bin": [ - "bin/ecs" - ], "type": "library", "autoload": { - "files": [ - "bootstrap.php" + "classmap": [ + "src/" ] }, "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.0.6" - }, - "funding": [ - { - "url": "https://www.paypal.me/rectorphp", - "type": "custom" - }, - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2023-07-25T17:12:00+00:00" - }, - { - "name": "vimeo/psalm", - "version": "5.15.0", - "source": { - "type": "git", - "url": "https://github.com/vimeo/psalm.git", - "reference": "5c774aca4746caf3d239d9c8cadb9f882ca29352" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/5c774aca4746caf3d239d9c8cadb9f882ca29352", - "reference": "5c774aca4746caf3d239d9c8cadb9f882ca29352", - "shasum": "" - }, - "require": { - "amphp/amp": "^2.4.2", - "amphp/byte-stream": "^1.5", - "composer-runtime-api": "^2", - "composer/semver": "^1.4 || ^2.0 || ^3.0", - "composer/xdebug-handler": "^2.0 || ^3.0", - "dnoegel/php-xdg-base-dir": "^0.1.1", - "ext-ctype": "*", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "ext-tokenizer": "*", - "felixfbecker/advanced-json-rpc": "^3.1", - "felixfbecker/language-server-protocol": "^1.5.2", - "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1", - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^4.16", - "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", - "sebastian/diff": "^4.0 || ^5.0", - "spatie/array-to-xml": "^2.17.0 || ^3.0", - "symfony/console": "^4.1.6 || ^5.0 || ^6.0", - "symfony/filesystem": "^5.4 || ^6.0" - }, - "conflict": { - "nikic/php-parser": "4.17.0" - }, - "provide": { - "psalm/psalm": "self.version" - }, - "require-dev": { - "amphp/phpunit-util": "^2.0", - "bamarni/composer-bin-plugin": "^1.4", - "brianium/paratest": "^6.9", - "ext-curl": "*", - "mockery/mockery": "^1.5", - "nunomaduro/mock-final-classes": "^1.1", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpdoc-parser": "^1.6", - "phpunit/phpunit": "^9.6", - "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.18", - "slevomat/coding-standard": "^8.4", - "squizlabs/php_codesniffer": "^3.6", - "symfony/process": "^4.4 || ^5.0 || ^6.0" - }, - "suggest": { - "ext-curl": "In order to send data to shepherd", - "ext-igbinary": "^2.0.5 is required, used to serialize caching data" - }, - "bin": [ - "psalm", - "psalm-language-server", - "psalm-plugin", - "psalm-refactor", - "psalter" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev", - "dev-4.x": "4.x-dev", - "dev-3.x": "3.x-dev", - "dev-2.x": "2.x-dev", - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psalm\\": "src/Psalm/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Matthew Brown" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" } ], - "description": "A static analysis tool for finding errors in PHP applications", - "keywords": [ - "code", - "inspection", - "php", - "static analysis" - ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { - "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/5.15.0" + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, - "time": "2023-08-20T23:07:30+00:00" - } - ], - "aliases": [ - { - "package": "dompdf/dompdf", - "version": "9999999-dev", - "alias": "v2.0.3", - "alias_normalized": "2.0.3.0" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" } ], + "aliases": [], "minimum-stability": "stable", "stability-flags": { - "dompdf/dompdf": 20, - "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": "*", @@ -16847,9 +21577,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 997ca15e..1d38a3f3 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 6545338d..ae7dc9cc 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -18,17 +18,19 @@ return [ DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], Gregwar\CaptchaBundle\GregwarCaptchaBundle::class => ['all' => true], - Translation\Bundle\TranslationBundle::class => ['all' => true], Florianv\SwapBundle\FlorianvSwapBundle::class => ['all' => true], Nelmio\SecurityBundle\NelmioSecurityBundle::class => ['all' => true], Symfony\UX\Turbo\TurboBundle::class => ['all' => true], Jbtronics\TFAWebauthn\TFAWebauthnBundle::class => ['all' => true], Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true], - SpomkyLabs\CborBundle\SpomkyLabsCborBundle::class => ['all' => true], Webauthn\Bundle\WebauthnBundle::class => ['all' => true], Nbgrp\OneloginSamlBundle\NbgrpOneloginSamlBundle::class => ['all' => true], Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true], Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true], Jbtronics\DompdfFontLoaderBundle\DompdfFontLoaderBundle::class => ['all' => true], KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true], + Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], + 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 new file mode 100644 index 00000000..1b679cd1 --- /dev/null +++ b/config/packages/api_platform.yaml @@ -0,0 +1,40 @@ +api_platform: + title: 'Part-DB API' + description: 'API of Part-DB' + version: '0.1.0' + + formats: + jsonld: ['application/ld+json'] + json: ['application/json'] + jsonapi: ['application/vnd.api+json'] + + docs_formats: + jsonld: ['application/ld+json'] + jsonopenapi: ['application/vnd.openapi+json'] + html: ['text/html'] + json: ['application/vnd.openapi+json'] + + swagger: + api_keys: + # overridden in OpenApiFactoryDecorator + access_token: + name: Authorization + type: header + + defaults: + # TODO: Change this to true later. In the moment it is false, because we use the session in somewhere + stateless: false + cache_headers: + vary: ['Content-Type', 'Authorization', 'Origin'] + extra_properties: + standard_put: true + rfc_7807_compliant_errors: true + + pagination_client_items_per_page: true # Allow clients to override the default items per page + + # Need to be true, or some tests will fail + 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 00000000..01db6267 --- /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/dama_doctrine_test_bundle.yaml b/config/packages/dama_doctrine_test_bundle.yaml new file mode 100644 index 00000000..3482cbae --- /dev/null +++ b/config/packages/dama_doctrine_test_bundle.yaml @@ -0,0 +1,5 @@ +when@test: + dama_doctrine_test: + enable_static_connection: true + enable_static_meta_data_cache: true + enable_static_query_cache: true diff --git a/config/packages/datatables.yaml b/config/packages/datatables.yaml index dc116f97..f1ea4715 100644 --- a/config/packages/datatables.yaml +++ b/config/packages/datatables.yaml @@ -8,17 +8,17 @@ 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 - #dom: "<'row' <'col-sm-12' tr>><'row' <'col-sm-6'l><'col-sm-6 text-right'pif>>" - dom: " <'row'<'col mb-2 input-group' B l> <'col mb-2' <'pull-end' p>>> - <'card' - rt - <'card-footer card-footer-table text-muted' i > - > - <'row'<'col mt-2 input-group' B l> <'col mt-2' <'pull-right' p>>>" + 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 + <'card-footer card-footer-table text-muted' i > + > + <'row' <'col mt-2 input-group flex-nowrap' B l > <'col-auto mt-2' < p >>>" pagingType: 'simple_numbers' - searching: true + searching: false stateSave: true diff --git a/config/packages/dev/php_translation.yaml b/config/packages/dev/php_translation.yaml deleted file mode 100644 index 53398fbc..00000000 --- a/config/packages/dev/php_translation.yaml +++ /dev/null @@ -1,5 +0,0 @@ -translation: - symfony_profiler: - enabled: true - webui: - enabled: true diff --git a/config/packages/doctrine.php b/config/packages/doctrine.php new file mode 100644 index 00000000..47584ed7 --- /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 2db28db2..5261c295 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -2,19 +2,29 @@ doctrine: dbal: url: '%env(resolve:DATABASE_URL)%' + # Required for DAMA doctrine test bundle + use_savepoints: true + # IMPORTANT: You MUST configure your server version, # either here or in the DATABASE_URL env var (see .env file) types: + # UTC datetimes datetime: class: App\Doctrine\Types\UTCDateTimeType date: class: App\Doctrine\Types\UTCDateTimeType + + datetime_immutable: + class: App\Doctrine\Types\UTCDateTimeImmutableType + date_immutable: + class: App\Doctrine\Types\UTCDateTimeImmutableType + big_decimal: class: App\Doctrine\Types\BigDecimalType tinyint: class: App\Doctrine\Types\TinyIntType - + schema_filter: ~^(?!internal)~ # Only enable this when needed profiling_collect_backtrace: false @@ -25,21 +35,27 @@ 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 mappings: App: - is_bundle: false type: attribute + is_bundle: false dir: '%kernel.project_dir%/src/Entity' prefix: 'App\Entity' alias: App dql: string_functions: - regexp: DoctrineExtensions\Query\Mysql\Regexp - ifnull: DoctrineExtensions\Query\Mysql\IfNull + regexp: App\Doctrine\Functions\Regexp field: DoctrineExtensions\Query\Mysql\Field field2: App\Doctrine\Functions\Field2 + natsort: App\Doctrine\Functions\Natsort + array_position: App\Doctrine\Functions\ArrayPosition + ilike: App\Doctrine\Functions\ILike when@test: doctrine: diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index a61fddc6..6843a177 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -1,10 +1,8 @@ # see https://symfony.com/doc/current/reference/configuration/framework.html framework: secret: '%env(APP_SECRET)%' - csrf_protection: true - handle_all_throwables: true - # We set this header by ourself, so we can disable it here + # We set this header by ourselves, so we can disable it here disallow_search_engine_index: false # Must be set to true, to enable the change of HTTP method via _method parameter, otherwise our delete routines does not work anymore @@ -26,12 +24,14 @@ framework: handler_id: null cookie_secure: auto cookie_samesite: lax - storage_factory_id: session.storage.factory.native #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 7d296a8b..5e56d5c5 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 8938cc13..387d71ad 100644 --- a/config/packages/monolog.yaml +++ b/config/packages/monolog.yaml @@ -10,14 +10,6 @@ when@dev: path: "%kernel.logs_dir%/%kernel.environment%.log" level: debug channels: ["!event"] - # uncomment to get logging in your browser - # you may have to allow bigger header sizes in your Web server configuration - #firephp: - # type: firephp - # level: info - #chromephp: - # type: chromephp - # level: info console: type: console process_psr_3_messages: false @@ -45,12 +37,12 @@ when@prod: action_level: error handler: nested excluded_http_codes: [404, 405] + channels: ["!deprecation"] buffer_size: 50 # How many messages should be saved? Prevent memory leaks nested: type: stream path: "%kernel.logs_dir%/%kernel.environment%.log" level: debug - formatter: monolog.formatter.json console: type: console process_psr_3_messages: false @@ -70,11 +62,11 @@ 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" level: debug - formatter: monolog.formatter.json console: type: console process_psr_3_messages: false diff --git a/config/packages/nbgrp_onelogin_saml.yaml b/config/packages/nbgrp_onelogin_saml.yaml index 66b133a2..2b1974f3 100644 --- a/config/packages/nbgrp_onelogin_saml.yaml +++ b/config/packages/nbgrp_onelogin_saml.yaml @@ -5,6 +5,7 @@ parameters: saml.sp.privateKey: '%env(string:SAML_SP_PRIVATE_KEY)%' nbgrp_onelogin_saml: + use_proxy_vars: '%env(bool:SAML_BEHIND_PROXY)%' onelogin_settings: default: # Basic settings @@ -31,7 +32,7 @@ nbgrp_onelogin_saml: privateKey: '%env(string:default:saml.sp.privateKey:string:SAMLP_SP_PRIVATE_KEY)%' # Optional settings - #baseurl: 'http://myapp.com' + baseurl: '%partdb.default_uri%saml/' strict: true debug: false security: diff --git a/config/packages/nelmio_cors.yaml b/config/packages/nelmio_cors.yaml new file mode 100644 index 00000000..c7665081 --- /dev/null +++ b/config/packages/nelmio_cors.yaml @@ -0,0 +1,10 @@ +nelmio_cors: + defaults: + origin_regex: true + allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] + allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] + allow_headers: ['Content-Type', 'Authorization'] + expose_headers: ['Link'] + max_age: 3600 + paths: + '^/': null diff --git a/config/packages/nelmio_security.yaml b/config/packages/nelmio_security.yaml index 075ce930..6b2b7337 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). @@ -51,12 +45,16 @@ nelmio_security: img-src: - '*' - 'data:' + # Required for be able to load pictures in the QR code scanner + - 'blob:' style-src: - 'self' - 'unsafe-inline' - 'data:' script-src: - 'self' + # Required for loading the Wasm for the barcode scanner: + - 'wasm-unsafe-eval' object-src: - 'self' - 'data:' @@ -65,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/php_translation.yaml b/config/packages/php_translation.yaml deleted file mode 100644 index 7c4f6ad9..00000000 --- a/config/packages/php_translation.yaml +++ /dev/null @@ -1,11 +0,0 @@ -translation: - locales: ["en", "de"] - edit_in_place: - enabled: false - config_name: app - configs: - app: - dirs: ["%kernel.project_dir%/templates", "%kernel.project_dir%/src"] - output_dir: "%kernel.project_dir%/translations" - excluded_names: ["*TestCase.php", "*Test.php"] - excluded_dirs: [cache, data, logs] diff --git a/config/packages/property_info.yaml b/config/packages/property_info.yaml new file mode 100644 index 00000000..dd31b9da --- /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 df5d98d2..0f34f872 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/scheb_2fa.yaml b/config/packages/scheb_2fa.yaml index 4359d0fb..ad0e7a5a 100644 --- a/config/packages/scheb_2fa.yaml +++ b/config/packages/scheb_2fa.yaml @@ -6,7 +6,7 @@ scheb_two_factor: server_name: '$$DOMAIN$$' # This field is replaced by the domain name of the server in DecoratedGoogleAuthenticator issuer: '%partdb.title%' # Issuer name used in QR code digits: 6 # Number of digits in authentication code - window: 1 # How many codes before/after the current one would be accepted as valid + leeway: 5 # Acceptable time drift in seconds template: security/2fa_form.html.twig backup_codes: diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 92b9f188..e7a44e0c 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -13,17 +13,20 @@ security: firewalls: dev: - pattern: ^/(_(profiler|wdt)|css|images|js)/ + pattern: ^/(_(profiler|wdt)|css|images|js|\.well-known)/ security: false main: provider: app_user_provider lazy: true user_checker: App\Security\UserChecker - entry_point: form_login + entry_point: App\Security\AuthenticationEntryPoint # Enable user impersonation switch_user: { role: CAN_SWITCH_USER } + custom_authenticators: + - App\Security\ApiTokenAuthenticator + two_factor: auth_form_path: 2fa_login check_path: 2fa_login_check @@ -66,3 +69,7 @@ security: # We get into trouble with the U2F authentication, if the calls to the trees trigger an 2FA login # This settings should not do much harm, because a read only access to show available data structures is not really critical - { path: "^/\\w{2}/tree", role: PUBLIC_ACCESS } + # Restrict access to API to users, which has the API access permission + - { path: "^/api", allow_if: 'is_granted("@api.access_api") and is_authenticated()' } + # Restrict access to KICAD to users, which has API access permission + - { path: "^/kicad-api", allow_if: 'is_granted("@api.access_api") and is_authenticated()' } diff --git a/config/packages/settings.yaml b/config/packages/settings.yaml new file mode 100644 index 00000000..c16d1804 --- /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 2767f740..4ef8fbdf 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 7266a176..cbc1cd7e 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 05dec32c..95ae4f3b 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -1,26 +1,23 @@ twig: default_path: '%kernel.project_dir%/templates' - form_themes: ['bootstrap_5_horizontal_layout.html.twig', 'form/extended_bootstrap_layout.html.twig', 'form/permission_layout.html.twig', 'form/filter_types_layout.html.twig'] + form_themes: ['bootstrap_5_horizontal_layout.html.twig', 'form/extended_bootstrap_layout.html.twig', 'form/permission_layout.html.twig', 'form/filter_types_layout.html.twig', 'form/synonyms_collection.html.twig'] paths: '%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%' saml_enabled: '%partdb.saml.enabled%' + part_preview_generator: '@App\Services\Attachments\PartPreviewGenerator' when@test: twig: - strict_variables: true \ No newline at end of file + strict_variables: true diff --git a/config/packages/uid.yaml b/config/packages/uid.yaml deleted file mode 100644 index 01520944..00000000 --- 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 00000000..c2a6a44e --- /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 0201281d..dd47a6ad 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 b9461110..15112444 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 84a07ab9..b79e2b88 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -5,26 +5,21 @@ 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', 'fr', 'ru', 'ja'] # The languages that are shown in user drop down menu - partdb.enforce_change_comments_for: '%env(csv:ENFORCE_CHANGE_COMMENTS_FOR)%' # The actions for which a change comment is required (e.g. "part_edit", "part_create", etc.). If this is empty, change comments are not required at all. - partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails + # 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(addSlash:string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails + + partdb.db.emulate_natural_sort: '%env(bool:DATABASE_EMULATE_NATURAL_SORT)%' # If this is set to true, natural sorting is emulated on platforms that do not support it natively. This can be slow on large datasets. ###################################################################################################################### # Users and Privacy ###################################################################################################################### 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 ###################################################################################################################### @@ -34,10 +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.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 @@ -50,21 +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 - - ###################################################################################################################### - # 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 @@ -106,28 +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(ENFORCE_CHANGE_COMMENTS_FOR): '' + env(REDIRECT_TO_HTTPS): 0 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 @@ -135,8 +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(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 cf363100..8c6a145e 100644 --- a/config/permissions.yaml +++ b/config/permissions.yaml @@ -18,34 +18,42 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co parts: # e.g. this maps to perms_parts in User/Group database group: "data" - label: "perm.parts" + label: "[[Part]]" operations: # Here are all possible operations are listed => the op name is mapped to bit value read: label: "perm.read" # If a part can be read by a user, he can also see all the datastructures (except devices) alsoSet: ['storelocations.read', 'footprints.read', 'categories.read', 'suppliers.read', 'manufacturers.read', - 'currencies.read', 'attachment_types.read', 'measurement_units.read'] + 'currencies.read', 'attachment_types.read', 'measurement_units.read', 'part_custom_states.read'] + apiTokenRole: ROLE_API_READ_ONLY edit: label: "perm.edit" alsoSet: ['read', 'parts_stock.withdraw', 'parts_stock.add', 'parts_stock.move'] + apiTokenRole: ROLE_API_EDIT create: label: "perm.create" alsoSet: ['read', 'edit'] + apiTokenRole: ROLE_API_EDIT delete: label: "perm.delete" alsoSet: ['read', 'edit'] + apiTokenRole: ROLE_API_EDIT change_favorite: label: "perm.part.change_favorite" alsoSet: ['edit'] + apiTokenRole: ROLE_API_EDIT show_history: label: "perm.part.show_history" alsoSet: ['read'] + apiTokenRole: ROLE_API_READ_ONLY revert_element: label: "perm.revert_elements" alsoSet: ["read", "edit", "create", "delete", "show_history"] + apiTokenRole: ROLE_API_EDIT import: label: "perm.import" alsoSet: ["read", "edit", "create"] + apiTokenRole: ROLE_API_EDIT parts_stock: group: "data" @@ -53,67 +61,81 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co operations: withdraw: label: "perm.parts_stock.withdraw" + apiTokenRole: ROLE_API_EDIT add: label: "perm.parts_stock.add" + apiTokenRole: ROLE_API_EDIT move: label: "perm.parts_stock.move" + apiTokenRole: ROLE_API_EDIT storelocations: &PART_CONTAINING - label: "perm.storelocations" + label: "[[Storage_location]]" group: "data" operations: read: label: "perm.read" + apiTokenRole: ROLE_API_READ_ONLY edit: label: "perm.edit" alsoSet: 'read' + apiTokenRole: ROLE_API_EDIT create: label: "perm.create" alsoSet: ['read', 'edit'] + apiTokenRole: ROLE_API_EDIT delete: label: "perm.delete" alsoSet: ['read', 'edit'] + apiTokenRole: ROLE_API_EDIT show_history: label: "perm.show_history" + apiTokenRole: ROLE_API_READ_ONLY revert_element: label: "perm.revert_elements" alsoSet: ["read", "edit", "create", "delete", "show_history"] + apiTokenRole: ROLE_API_EDIT import: label: "perm.import" alsoSet: [ "read", "edit", "create" ] + apiTokenRole: ROLE_API_EDIT footprints: <<: *PART_CONTAINING - label: "perm.part.footprints" + label: "[[Footprint]]" categories: <<: *PART_CONTAINING - label: "perm.part.categories" + label: "[[Category]]" suppliers: <<: *PART_CONTAINING - label: "perm.part.supplier" + label: "[[Supplier]]" manufacturers: <<: *PART_CONTAINING - label: "perm.part.manufacturers" + label: "[[Manufacturer]]" projects: <<: *PART_CONTAINING - label: "perm.projects" + label: "[[Project]]" attachment_types: <<: *PART_CONTAINING - label: "perm.part.attachment_types" + label: "[[Attachment_type]]" currencies: <<: *PART_CONTAINING - label: "perm.currencies" + label: "[[Currency]]" measurement_units: <<: *PART_CONTAINING - label: "perm.measurement_units" + label: "[[Measurement_unit]]" + + part_custom_states: + <<: *PART_CONTAINING + label: "[[Part_custom_state]]" tools: label: "perm.part.tools" @@ -145,6 +167,7 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co create_parts: label: "perm.part.info_providers.create_parts" alsoSet: ['parts.create'] + apiTokenRole: ROLE_API_EDIT groups: label: "perm.groups" @@ -152,26 +175,34 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co operations: read: label: "perm.read" + apiTokenRole: ROLE_API_ADMIN edit: label: "perm.edit" alsoSet: 'read' + apiTokenRole: ROLE_API_ADMIN create: label: "perm.create" alsoSet: ['read', 'edit'] + apiTokenRole: ROLE_API_ADMIN delete: label: "perm.delete" alsoSet: ['read', 'delete'] + apiTokenRole: ROLE_API_ADMIN edit_permissions: label: "perm.edit_permissions" alsoSet: ['read', 'edit'] + apiTokenRole: ROLE_API_ADMIN show_history: label: "perm.show_history" + apiTokenRole: ROLE_API_ADMIN revert_element: label: "perm.revert_elements" alsoSet: ["read", "edit", "create", "delete", "edit_permissions", "show_history"] + apiTokenRole: ROLE_API_ADMIN import: label: "perm.import" alsoSet: [ "read", "edit", "create" ] + apiTokenRole: ROLE_API_ADMIN users: label: "perm.users" @@ -179,37 +210,49 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co operations: read: label: "perm.read" + apiTokenRole: ROLE_API_ADMIN create: label: "perm.create" alsoSet: ['read', 'edit_username', 'edit_infos'] + apiTokenRole: ROLE_API_ADMIN delete: label: "perm.delete" alsoSet: ['read', 'edit_username', 'edit_infos'] + apiTokenRole: ROLE_API_ADMIN edit_username: label: "perm.users.edit_user_name" alsoSet: ['read'] + apiTokenRole: ROLE_API_ADMIN edit_infos: label: "perm.users.edit_infos" alsoSet: 'read' + apiTokenRole: ROLE_API_ADMIN edit_permissions: label: "perm.users.edit_permissions" alsoSet: 'read' + apiTokenRole: ROLE_API_ADMIN set_password: label: "perm.users.set_password" alsoSet: 'read' + apiTokenRole: ROLE_API_FULL impersonate: label: "perm.users.impersonate" alsoSet: ['set_password'] + apiTokenRole: ROLE_API_FULL change_user_settings: label: "perm.users.change_user_settings" + apiTokenRole: ROLE_API_ADMIN show_history: label: "perm.show_history" + apiTokenRole: ROLE_API_ADMIN revert_element: label: "perm.revert_elements" alsoSet: ["read", "create", "delete", "edit_permissions", "show_history", "edit_infos", "edit_username"] + apiTokenRole: ROLE_API_ADMIN import: label: "perm.import" alsoSet: [ "read", "create" ] + apiTokenRole: ROLE_API_ADMIN #database: # label: "perm.database" @@ -226,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" @@ -244,64 +283,98 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co operations: show_logs: label: "perm.show_logs" + apiTokenRole: ROLE_API_ADMIN delete_logs: label: "perm.delete_logs" alsoSet: 'show_logs' + apiTokenRole: ROLE_API_ADMIN server_infos: label: "perm.server_infos" + apiTokenRole: ROLE_API_ADMIN manage_oauth_tokens: label: "Manage OAuth tokens" + apiTokenRole: ROLE_API_ADMIN show_updates: label: "perm.system.show_available_updates" + apiTokenRole: ROLE_API_ADMIN + attachments: label: "perm.part.attachments" operations: show_private: label: "perm.attachments.show_private" + apiTokenRole: ROLE_API_READ_ONLY list_attachments: label: "perm.attachments.list_attachments" alsoSet: ['attachment_types.read'] + apiTokenRole: ROLE_API_READ_ONLY self: label: "perm.self" operations: edit_infos: label: "perm.self.edit_infos" + apiTokenRole: ROLE_API_FULL edit_username: label: "perm.self.edit_username" + apiTokenRole: ROLE_API_FULL show_permissions: label: "perm.self.show_permissions" + apiTokenRole: ROLE_API_READ_ONLY show_logs: label: "perm.self.show_logs" + apiTokenRole: ROLE_API_FULL labels: label: "perm.labels" operations: create_labels: label: "perm.self.create_labels" + apiTokenRole: ROLE_API_READ_ONLY edit_options: label: "perm.self.edit_options" alsoSet: ['create_labels'] + apiTokenRole: ROLE_API_READ_ONLY read_profiles: label: "perm.self.read_profiles" + apiTokenRole: ROLE_API_READ_ONLY edit_profiles: label: "perm.self.edit_profiles" alsoSet: ['read_profiles'] + apiTokenRole: ROLE_API_EDIT create_profiles: label: "perm.self.create_profiles" alsoSet: ['read_profiles', 'edit_profiles'] + apiTokenRole: ROLE_API_EDIT delete_profiles: label: "perm.self.delete_profiles" alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles'] + apiTokenRole: ROLE_API_EDIT use_twig: label: "perm.labels.use_twig" alsoSet: ['create_labels', 'edit_options'] + apiTokenRole: ROLE_API_ADMIN show_history: label: "perm.show_history" alsoSet: ['read_profiles'] + apiTokenRole: ROLE_API_READ_ONLY revert_element: label: "perm.revert_elements" alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles', 'delete_profiles'] + apiTokenRole: ROLE_API_EDIT + import: + label: "perm.import" + alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles' ] + apiTokenRole: ROLE_API_EDIT - + api: + label: "perm.api" + operations: + access_api: + label: "perm.api.access_api" + apiTokenRole: ROLE_API_READ_ONLY + manage_tokens: + label: "perm.api.manage_tokens" + alsoSet: ['access_api'] + apiTokenRole: ROLE_API_FULL diff --git a/config/reference.php b/config/reference.php new file mode 100644 index 00000000..3ed46fd1 --- /dev/null +++ b/config/reference.php @@ -0,0 +1,2898 @@ + [ + * 'App\\' => [ + * 'resource' => '../src/', + * ], + * ], + * ]); + * ``` + * + * @psalm-type ImportsConfig = list + * @psalm-type ParametersConfig = array|null>|null> + * @psalm-type ArgumentsType = list|array + * @psalm-type CallType = array|array{0:string, 1?:ArgumentsType, 2?:bool}|array{method:string, arguments?:ArgumentsType, returns_clone?:bool} + * @psalm-type TagsType = list>> // arrays inside the list must have only one element, with the tag name as the key + * @psalm-type CallbackType = string|array{0:string|ReferenceConfigurator,1:string}|\Closure|ReferenceConfigurator|ExpressionConfigurator + * @psalm-type DeprecationType = array{package: string, version: string, message?: string} + * @psalm-type DefaultsType = array{ + * public?: bool, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * } + * @psalm-type InstanceofType = array{ + * shared?: bool, + * lazy?: bool|string, + * public?: bool, + * properties?: array, + * configurator?: CallbackType, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * bind?: array, + * constructor?: string, + * } + * @psalm-type DefinitionType = array{ + * class?: string, + * file?: string, + * parent?: string, + * shared?: bool, + * synthetic?: bool, + * lazy?: bool|string, + * public?: bool, + * abstract?: bool, + * deprecated?: DeprecationType, + * factory?: CallbackType, + * configurator?: CallbackType, + * arguments?: ArgumentsType, + * properties?: array, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * decorates?: string, + * decoration_inner_name?: string, + * decoration_priority?: int, + * decoration_on_invalid?: 'exception'|'ignore'|null, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * constructor?: string, + * from_callable?: CallbackType, + * } + * @psalm-type AliasType = string|array{ + * alias: string, + * public?: bool, + * deprecated?: DeprecationType, + * } + * @psalm-type PrototypeType = array{ + * resource: string, + * namespace?: string, + * exclude?: string|list, + * parent?: string, + * shared?: bool, + * lazy?: bool|string, + * public?: bool, + * abstract?: bool, + * deprecated?: DeprecationType, + * factory?: CallbackType, + * arguments?: ArgumentsType, + * properties?: array, + * configurator?: CallbackType, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * constructor?: string, + * } + * @psalm-type StackType = array{ + * stack: list>, + * public?: bool, + * deprecated?: DeprecationType, + * } + * @psalm-type ServicesConfig = array{ + * _defaults?: DefaultsType, + * _instanceof?: InstanceofType, + * ... + * } + * @psalm-type ExtensionType = array + * @psalm-type FrameworkConfig = array{ + * secret?: scalar|null|Param, + * http_method_override?: bool|Param, // Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. // Default: false + * allowed_http_method_override?: list|null, + * trust_x_sendfile_type_header?: scalar|null|Param, // Set true to enable support for xsendfile in binary file responses. // Default: "%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%" + * ide?: scalar|null|Param, // Default: "%env(default::SYMFONY_IDE)%" + * test?: bool|Param, + * default_locale?: scalar|null|Param, // Default: "en" + * set_locale_from_accept_language?: bool|Param, // Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed). // Default: false + * set_content_language_from_locale?: bool|Param, // Whether to set the Content-Language HTTP header on the Response using the Request locale. // Default: false + * enabled_locales?: list, + * trusted_hosts?: list, + * trusted_proxies?: mixed, // Default: ["%env(default::SYMFONY_TRUSTED_PROXIES)%"] + * trusted_headers?: list, + * error_controller?: scalar|null|Param, // Default: "error_controller" + * handle_all_throwables?: bool|Param, // HttpKernel will handle all kinds of \Throwable. // Default: true + * csrf_protection?: bool|array{ + * enabled?: scalar|null|Param, // Default: null + * stateless_token_ids?: list, + * check_header?: scalar|null|Param, // Whether to check the CSRF token in a header in addition to a cookie when using stateless protection. // Default: false + * cookie_name?: scalar|null|Param, // The name of the cookie to use when using stateless protection. // Default: "csrf-token" + * }, + * form?: bool|array{ // Form configuration + * enabled?: bool|Param, // Default: true + * csrf_protection?: array{ + * enabled?: scalar|null|Param, // Default: null + * token_id?: scalar|null|Param, // Default: null + * field_name?: scalar|null|Param, // Default: "_token" + * field_attr?: array, + * }, + * }, + * http_cache?: bool|array{ // HTTP cache configuration + * enabled?: bool|Param, // Default: false + * debug?: bool|Param, // Default: "%kernel.debug%" + * trace_level?: "none"|"short"|"full"|Param, + * trace_header?: scalar|null|Param, + * default_ttl?: int|Param, + * private_headers?: list, + * skip_response_headers?: list, + * allow_reload?: bool|Param, + * allow_revalidate?: bool|Param, + * stale_while_revalidate?: int|Param, + * stale_if_error?: int|Param, + * terminate_on_cache_hit?: bool|Param, + * }, + * esi?: bool|array{ // ESI configuration + * enabled?: bool|Param, // Default: false + * }, + * ssi?: bool|array{ // SSI configuration + * enabled?: bool|Param, // Default: false + * }, + * fragments?: bool|array{ // Fragments configuration + * enabled?: bool|Param, // Default: false + * hinclude_default_template?: scalar|null|Param, // Default: null + * path?: scalar|null|Param, // Default: "/_fragment" + * }, + * profiler?: bool|array{ // Profiler configuration + * enabled?: bool|Param, // Default: false + * collect?: bool|Param, // Default: true + * collect_parameter?: scalar|null|Param, // The name of the parameter to use to enable or disable collection on a per request basis. // Default: null + * only_exceptions?: bool|Param, // Default: false + * only_main_requests?: bool|Param, // Default: false + * dsn?: scalar|null|Param, // Default: "file:%kernel.cache_dir%/profiler" + * collect_serializer_data?: bool|Param, // Enables the serializer data collector and profiler panel. // Default: false + * }, + * workflows?: bool|array{ + * enabled?: bool|Param, // Default: false + * workflows?: array, + * definition_validators?: list, + * support_strategy?: scalar|null|Param, + * initial_marking?: list, + * events_to_dispatch?: list|null, + * places?: list, + * }>, + * transitions: list, + * to?: list, + * weight?: int|Param, // Default: 1 + * metadata?: list, + * }>, + * metadata?: list, + * }>, + * }, + * router?: bool|array{ // Router configuration + * enabled?: bool|Param, // Default: false + * resource: scalar|null|Param, + * type?: scalar|null|Param, + * cache_dir?: scalar|null|Param, // Deprecated: Setting the "framework.router.cache_dir.cache_dir" configuration option is deprecated. It will be removed in version 8.0. // Default: "%kernel.build_dir%" + * default_uri?: scalar|null|Param, // The default URI used to generate URLs in a non-HTTP context. // Default: null + * http_port?: scalar|null|Param, // Default: 80 + * https_port?: scalar|null|Param, // Default: 443 + * strict_requirements?: scalar|null|Param, // set to true to throw an exception when a parameter does not match the requirements set to false to disable exceptions when a parameter does not match the requirements (and return null instead) set to null to disable parameter checks against requirements 'true' is the preferred configuration in development mode, while 'false' or 'null' might be preferred in production // Default: true + * utf8?: bool|Param, // Default: true + * }, + * session?: bool|array{ // Session configuration + * enabled?: bool|Param, // Default: false + * storage_factory_id?: scalar|null|Param, // Default: "session.storage.factory.native" + * handler_id?: scalar|null|Param, // Defaults to using the native session handler, or to the native *file* session handler if "save_path" is not null. + * name?: scalar|null|Param, + * cookie_lifetime?: scalar|null|Param, + * cookie_path?: scalar|null|Param, + * cookie_domain?: scalar|null|Param, + * cookie_secure?: true|false|"auto"|Param, // Default: "auto" + * cookie_httponly?: bool|Param, // Default: true + * cookie_samesite?: null|"lax"|"strict"|"none"|Param, // Default: "lax" + * use_cookies?: bool|Param, + * gc_divisor?: scalar|null|Param, + * gc_probability?: scalar|null|Param, + * gc_maxlifetime?: scalar|null|Param, + * save_path?: scalar|null|Param, // Defaults to "%kernel.cache_dir%/sessions" if the "handler_id" option is not null. + * metadata_update_threshold?: int|Param, // Seconds to wait between 2 session metadata updates. // Default: 0 + * sid_length?: int|Param, // Deprecated: Setting the "framework.session.sid_length.sid_length" configuration option is deprecated. It will be removed in version 8.0. No alternative is provided as PHP 8.4 has deprecated the related option. + * sid_bits_per_character?: int|Param, // Deprecated: Setting the "framework.session.sid_bits_per_character.sid_bits_per_character" configuration option is deprecated. It will be removed in version 8.0. No alternative is provided as PHP 8.4 has deprecated the related option. + * }, + * request?: bool|array{ // Request configuration + * enabled?: bool|Param, // Default: false + * formats?: array>, + * }, + * assets?: bool|array{ // Assets configuration + * enabled?: bool|Param, // Default: true + * strict_mode?: bool|Param, // Throw an exception if an entry is missing from the manifest.json. // Default: false + * version_strategy?: scalar|null|Param, // Default: null + * version?: scalar|null|Param, // Default: null + * version_format?: scalar|null|Param, // Default: "%%s?%%s" + * json_manifest_path?: scalar|null|Param, // Default: null + * base_path?: scalar|null|Param, // Default: "" + * base_urls?: list, + * packages?: array, + * }>, + * }, + * asset_mapper?: bool|array{ // Asset Mapper configuration + * enabled?: bool|Param, // Default: false + * paths?: array, + * excluded_patterns?: list, + * exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true + * server?: bool|Param, // If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default). // Default: true + * public_prefix?: scalar|null|Param, // The public path where the assets will be written to (and served from when "server" is true). // Default: "/assets/" + * missing_import_mode?: "strict"|"warn"|"ignore"|Param, // Behavior if an asset cannot be found when imported from JavaScript or CSS files - e.g. "import './non-existent.js'". "strict" means an exception is thrown, "warn" means a warning is logged, "ignore" means the import is left as-is. // Default: "warn" + * extensions?: array, + * importmap_path?: scalar|null|Param, // The path of the importmap.php file. // Default: "%kernel.project_dir%/importmap.php" + * importmap_polyfill?: scalar|null|Param, // The importmap name that will be used to load the polyfill. Set to false to disable. // Default: "es-module-shims" + * importmap_script_attributes?: array, + * vendor_dir?: scalar|null|Param, // The directory to store JavaScript vendors. // Default: "%kernel.project_dir%/assets/vendor" + * precompress?: bool|array{ // Precompress assets with Brotli, Zstandard and gzip. + * enabled?: bool|Param, // Default: false + * formats?: list, + * extensions?: list, + * }, + * }, + * translator?: bool|array{ // Translator configuration + * enabled?: bool|Param, // Default: true + * fallbacks?: list, + * logging?: bool|Param, // Default: false + * formatter?: scalar|null|Param, // Default: "translator.formatter.default" + * cache_dir?: scalar|null|Param, // Default: "%kernel.cache_dir%/translations" + * default_path?: scalar|null|Param, // The default path used to load translations. // Default: "%kernel.project_dir%/translations" + * paths?: list, + * pseudo_localization?: bool|array{ + * enabled?: bool|Param, // Default: false + * accents?: bool|Param, // Default: true + * expansion_factor?: float|Param, // Default: 1.0 + * brackets?: bool|Param, // Default: true + * parse_html?: bool|Param, // Default: false + * localizable_html_attributes?: list, + * }, + * providers?: array, + * locales?: list, + * }>, + * globals?: array, + * domain?: string|Param, + * }>, + * }, + * validation?: bool|array{ // Validation configuration + * enabled?: bool|Param, // Default: true + * cache?: scalar|null|Param, // Deprecated: Setting the "framework.validation.cache.cache" configuration option is deprecated. It will be removed in version 8.0. + * enable_attributes?: bool|Param, // Default: true + * static_method?: list, + * translation_domain?: scalar|null|Param, // Default: "validators" + * email_validation_mode?: "html5"|"html5-allow-no-tld"|"strict"|"loose"|Param, // Default: "html5" + * mapping?: array{ + * paths?: list, + * }, + * not_compromised_password?: bool|array{ + * enabled?: bool|Param, // When disabled, compromised passwords will be accepted as valid. // Default: true + * endpoint?: scalar|null|Param, // API endpoint for the NotCompromisedPassword Validator. // Default: null + * }, + * disable_translation?: bool|Param, // Default: false + * auto_mapping?: array, + * }>, + * }, + * annotations?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * serializer?: bool|array{ // Serializer configuration + * enabled?: bool|Param, // Default: true + * enable_attributes?: bool|Param, // Default: true + * name_converter?: scalar|null|Param, + * circular_reference_handler?: scalar|null|Param, + * max_depth_handler?: scalar|null|Param, + * mapping?: array{ + * paths?: list, + * }, + * default_context?: list, + * named_serializers?: array, + * include_built_in_normalizers?: bool|Param, // Whether to include the built-in normalizers // Default: true + * include_built_in_encoders?: bool|Param, // Whether to include the built-in encoders // Default: true + * }>, + * }, + * property_access?: bool|array{ // Property access configuration + * enabled?: bool|Param, // Default: true + * magic_call?: bool|Param, // Default: false + * magic_get?: bool|Param, // Default: true + * magic_set?: bool|Param, // Default: true + * throw_exception_on_invalid_index?: bool|Param, // Default: false + * throw_exception_on_invalid_property_path?: bool|Param, // Default: true + * }, + * type_info?: bool|array{ // Type info configuration + * enabled?: bool|Param, // Default: true + * aliases?: array, + * }, + * property_info?: bool|array{ // Property info configuration + * enabled?: bool|Param, // Default: true + * with_constructor_extractor?: bool|Param, // Registers the constructor extractor. + * }, + * cache?: array{ // Cache configuration + * prefix_seed?: scalar|null|Param, // Used to namespace cache keys when using several apps with the same shared backend. // Default: "_%kernel.project_dir%.%kernel.container_class%" + * app?: scalar|null|Param, // App related cache pools configuration. // Default: "cache.adapter.filesystem" + * system?: scalar|null|Param, // System related cache pools configuration. // Default: "cache.adapter.system" + * directory?: scalar|null|Param, // Default: "%kernel.share_dir%/pools/app" + * default_psr6_provider?: scalar|null|Param, + * default_redis_provider?: scalar|null|Param, // Default: "redis://localhost" + * default_valkey_provider?: scalar|null|Param, // Default: "valkey://localhost" + * default_memcached_provider?: scalar|null|Param, // Default: "memcached://localhost" + * default_doctrine_dbal_provider?: scalar|null|Param, // Default: "database_connection" + * default_pdo_provider?: scalar|null|Param, // Default: null + * pools?: array, + * tags?: scalar|null|Param, // Default: null + * public?: bool|Param, // Default: false + * default_lifetime?: scalar|null|Param, // Default lifetime of the pool. + * provider?: scalar|null|Param, // Overwrite the setting from the default provider for this adapter. + * early_expiration_message_bus?: scalar|null|Param, + * clearer?: scalar|null|Param, + * }>, + * }, + * php_errors?: array{ // PHP errors handling configuration + * log?: mixed, // Use the application logger instead of the PHP logger for logging PHP errors. // Default: true + * throw?: bool|Param, // Throw PHP errors as \ErrorException instances. // Default: true + * }, + * exceptions?: array, + * web_link?: bool|array{ // Web links configuration + * enabled?: bool|Param, // Default: true + * }, + * lock?: bool|string|array{ // Lock configuration + * enabled?: bool|Param, // Default: false + * resources?: array>, + * }, + * semaphore?: bool|string|array{ // Semaphore configuration + * enabled?: bool|Param, // Default: false + * resources?: array, + * }, + * messenger?: bool|array{ // Messenger configuration + * enabled?: bool|Param, // Default: false + * routing?: array, + * }>, + * serializer?: array{ + * default_serializer?: scalar|null|Param, // Service id to use as the default serializer for the transports. // Default: "messenger.transport.native_php_serializer" + * symfony_serializer?: array{ + * format?: scalar|null|Param, // Serialization format for the messenger.transport.symfony_serializer service (which is not the serializer used by default). // Default: "json" + * context?: array, + * }, + * }, + * transports?: array, + * failure_transport?: scalar|null|Param, // Transport name to send failed messages to (after all retries have failed). // Default: null + * retry_strategy?: string|array{ + * service?: scalar|null|Param, // Service id to override the retry strategy entirely. // Default: null + * max_retries?: int|Param, // Default: 3 + * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries)). // Default: 2 + * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float|Param, // Randomness to apply to the delay (between 0 and 1). // Default: 0.1 + * }, + * rate_limiter?: scalar|null|Param, // Rate limiter name to use when processing messages. // Default: null + * }>, + * failure_transport?: scalar|null|Param, // Transport name to send failed messages to (after all retries have failed). // Default: null + * stop_worker_on_signals?: list, + * default_bus?: scalar|null|Param, // Default: null + * buses?: array, + * }>, + * }>, + * }, + * scheduler?: bool|array{ // Scheduler configuration + * enabled?: bool|Param, // Default: false + * }, + * disallow_search_engine_index?: bool|Param, // Enabled by default when debug is enabled. // Default: true + * http_client?: bool|array{ // HTTP Client configuration + * enabled?: bool|Param, // Default: true + * max_host_connections?: int|Param, // The maximum number of connections to a single host. + * default_options?: array{ + * headers?: array, + * vars?: array, + * max_redirects?: int|Param, // The maximum number of redirects to follow. + * http_version?: scalar|null|Param, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * resolve?: array, + * proxy?: scalar|null|Param, // The URL of the proxy to pass requests through or null for automatic detection. + * no_proxy?: scalar|null|Param, // A comma separated list of hosts that do not require a proxy to be reached. + * timeout?: float|Param, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * max_duration?: float|Param, // The maximum execution time for the request+response as a whole. + * bindto?: scalar|null|Param, // A network interface name, IP address, a host name or a UNIX socket to bind to. + * verify_peer?: bool|Param, // Indicates if the peer should be verified in a TLS context. + * verify_host?: bool|Param, // Indicates if the host should exist as a certificate common name. + * cafile?: scalar|null|Param, // A certificate authority file. + * capath?: scalar|null|Param, // A directory that contains multiple certificate authority files. + * local_cert?: scalar|null|Param, // A PEM formatted certificate file. + * local_pk?: scalar|null|Param, // A private key file. + * passphrase?: scalar|null|Param, // The passphrase used to encrypt the "local_pk" file. + * ciphers?: scalar|null|Param, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...) + * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). + * sha1?: mixed, + * pin-sha256?: mixed, + * md5?: mixed, + * }, + * crypto_method?: scalar|null|Param, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. + * extra?: array, + * rate_limiter?: scalar|null|Param, // Rate limiter name to use for throttling requests. // Default: null + * caching?: bool|array{ // Caching configuration. + * enabled?: bool|Param, // Default: false + * cache_pool?: string|Param, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" + * shared?: bool|Param, // Indicates whether the cache is shared (public) or private. // Default: true + * max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null + * }, + * retry_failed?: bool|array{ + * enabled?: bool|Param, // Default: false + * retry_strategy?: scalar|null|Param, // service id to override the retry strategy. // Default: null + * http_codes?: array, + * }>, + * max_retries?: int|Param, // Default: 3 + * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 + * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float|Param, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 + * }, + * }, + * mock_response_factory?: scalar|null|Param, // The id of the service that should generate mock responses. It should be either an invokable or an iterable. + * scoped_clients?: array, + * headers?: array, + * max_redirects?: int|Param, // The maximum number of redirects to follow. + * http_version?: scalar|null|Param, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * resolve?: array, + * proxy?: scalar|null|Param, // The URL of the proxy to pass requests through or null for automatic detection. + * no_proxy?: scalar|null|Param, // A comma separated list of hosts that do not require a proxy to be reached. + * timeout?: float|Param, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * max_duration?: float|Param, // The maximum execution time for the request+response as a whole. + * bindto?: scalar|null|Param, // A network interface name, IP address, a host name or a UNIX socket to bind to. + * verify_peer?: bool|Param, // Indicates if the peer should be verified in a TLS context. + * verify_host?: bool|Param, // Indicates if the host should exist as a certificate common name. + * cafile?: scalar|null|Param, // A certificate authority file. + * capath?: scalar|null|Param, // A directory that contains multiple certificate authority files. + * local_cert?: scalar|null|Param, // A PEM formatted certificate file. + * local_pk?: scalar|null|Param, // A private key file. + * passphrase?: scalar|null|Param, // The passphrase used to encrypt the "local_pk" file. + * ciphers?: scalar|null|Param, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...). + * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). + * sha1?: mixed, + * pin-sha256?: mixed, + * md5?: mixed, + * }, + * crypto_method?: scalar|null|Param, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. + * extra?: array, + * rate_limiter?: scalar|null|Param, // Rate limiter name to use for throttling requests. // Default: null + * caching?: bool|array{ // Caching configuration. + * enabled?: bool|Param, // Default: false + * cache_pool?: string|Param, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" + * shared?: bool|Param, // Indicates whether the cache is shared (public) or private. // Default: true + * max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null + * }, + * retry_failed?: bool|array{ + * enabled?: bool|Param, // Default: false + * retry_strategy?: scalar|null|Param, // service id to override the retry strategy. // Default: null + * http_codes?: array, + * }>, + * max_retries?: int|Param, // Default: 3 + * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 + * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float|Param, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 + * }, + * }>, + * }, + * mailer?: bool|array{ // Mailer configuration + * enabled?: bool|Param, // Default: true + * message_bus?: scalar|null|Param, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null + * dsn?: scalar|null|Param, // Default: null + * transports?: array, + * envelope?: array{ // Mailer Envelope configuration + * sender?: scalar|null|Param, + * recipients?: list, + * allowed_recipients?: list, + * }, + * headers?: array, + * dkim_signer?: bool|array{ // DKIM signer configuration + * enabled?: bool|Param, // Default: false + * key?: scalar|null|Param, // Key content, or path to key (in PEM format with the `file://` prefix) // Default: "" + * domain?: scalar|null|Param, // Default: "" + * select?: scalar|null|Param, // Default: "" + * passphrase?: scalar|null|Param, // The private key passphrase // Default: "" + * options?: array, + * }, + * smime_signer?: bool|array{ // S/MIME signer configuration + * enabled?: bool|Param, // Default: false + * key?: scalar|null|Param, // Path to key (in PEM format) // Default: "" + * certificate?: scalar|null|Param, // Path to certificate (in PEM format without the `file://` prefix) // Default: "" + * passphrase?: scalar|null|Param, // The private key passphrase // Default: null + * extra_certificates?: scalar|null|Param, // Default: null + * sign_options?: int|Param, // Default: null + * }, + * smime_encrypter?: bool|array{ // S/MIME encrypter configuration + * enabled?: bool|Param, // Default: false + * repository?: scalar|null|Param, // S/MIME certificate repository service. This service shall implement the `Symfony\Component\Mailer\EventListener\SmimeCertificateRepositoryInterface`. // Default: "" + * cipher?: int|Param, // A set of algorithms used to encrypt the message // Default: null + * }, + * }, + * secrets?: bool|array{ + * enabled?: bool|Param, // Default: true + * vault_directory?: scalar|null|Param, // Default: "%kernel.project_dir%/config/secrets/%kernel.runtime_environment%" + * local_dotenv_file?: scalar|null|Param, // Default: "%kernel.project_dir%/.env.%kernel.runtime_environment%.local" + * decryption_env_var?: scalar|null|Param, // Default: "base64:default::SYMFONY_DECRYPTION_SECRET" + * }, + * notifier?: bool|array{ // Notifier configuration + * enabled?: bool|Param, // Default: false + * message_bus?: scalar|null|Param, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null + * chatter_transports?: array, + * texter_transports?: array, + * notification_on_failed_messages?: bool|Param, // Default: false + * channel_policy?: array>, + * admin_recipients?: list, + * }, + * rate_limiter?: bool|array{ // Rate limiter configuration + * enabled?: bool|Param, // Default: true + * limiters?: array, + * limit?: int|Param, // The maximum allowed hits in a fixed interval or burst. + * interval?: scalar|null|Param, // Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * rate?: array{ // Configures the fill rate if "policy" is set to "token_bucket". + * interval?: scalar|null|Param, // Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * amount?: int|Param, // Amount of tokens to add each interval. // Default: 1 + * }, + * }>, + * }, + * uid?: bool|array{ // Uid configuration + * enabled?: bool|Param, // Default: true + * default_uuid_version?: 7|6|4|1|Param, // Default: 7 + * name_based_uuid_version?: 5|3|Param, // Default: 5 + * name_based_uuid_namespace?: scalar|null|Param, + * time_based_uuid_version?: 7|6|1|Param, // Default: 7 + * time_based_uuid_node?: scalar|null|Param, + * }, + * html_sanitizer?: bool|array{ // HtmlSanitizer configuration + * enabled?: bool|Param, // Default: false + * sanitizers?: array, + * block_elements?: list, + * drop_elements?: list, + * allow_attributes?: array, + * drop_attributes?: array, + * force_attributes?: array>, + * force_https_urls?: bool|Param, // Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. // Default: false + * allowed_link_schemes?: list, + * allowed_link_hosts?: list|null, + * allow_relative_links?: bool|Param, // Allows relative URLs to be used in links href attributes. // Default: false + * allowed_media_schemes?: list, + * allowed_media_hosts?: list|null, + * allow_relative_medias?: bool|Param, // Allows relative URLs to be used in media source attributes (img, audio, video, ...). // Default: false + * with_attribute_sanitizers?: list, + * without_attribute_sanitizers?: list, + * max_input_length?: int|Param, // The maximum length allowed for the sanitized input. // Default: 0 + * }>, + * }, + * webhook?: bool|array{ // Webhook configuration + * enabled?: bool|Param, // Default: false + * message_bus?: scalar|null|Param, // The message bus to use. // Default: "messenger.default_bus" + * routing?: array, + * }, + * remote-event?: bool|array{ // RemoteEvent configuration + * enabled?: bool|Param, // Default: false + * }, + * json_streamer?: bool|array{ // JSON streamer configuration + * enabled?: bool|Param, // Default: false + * }, + * } + * @psalm-type DoctrineConfig = array{ + * dbal?: array{ + * default_connection?: scalar|null|Param, + * types?: array, + * driver_schemes?: array, + * connections?: array, + * mapping_types?: array, + * default_table_options?: array, + * schema_manager_factory?: scalar|null|Param, // Default: "doctrine.dbal.default_schema_manager_factory" + * result_cache?: scalar|null|Param, + * slaves?: array, + * replicas?: array, + * }>, + * }, + * orm?: array{ + * default_entity_manager?: scalar|null|Param, + * auto_generate_proxy_classes?: scalar|null|Param, // Auto generate mode possible values are: "NEVER", "ALWAYS", "FILE_NOT_EXISTS", "EVAL", "FILE_NOT_EXISTS_OR_CHANGED", this option is ignored when the "enable_native_lazy_objects" option is true // Default: false + * enable_lazy_ghost_objects?: bool|Param, // Enables the new implementation of proxies based on lazy ghosts instead of using the legacy implementation // Default: true + * enable_native_lazy_objects?: bool|Param, // Enables the new native implementation of PHP lazy objects instead of generated proxies // Default: false + * proxy_dir?: scalar|null|Param, // Configures the path where generated proxy classes are saved when using non-native lazy objects, this option is ignored when the "enable_native_lazy_objects" option is true // Default: "%kernel.build_dir%/doctrine/orm/Proxies" + * proxy_namespace?: scalar|null|Param, // Defines the root namespace for generated proxy classes when using non-native lazy objects, this option is ignored when the "enable_native_lazy_objects" option is true // Default: "Proxies" + * controller_resolver?: bool|array{ + * enabled?: bool|Param, // Default: true + * auto_mapping?: bool|null|Param, // Set to false to disable using route placeholders as lookup criteria when the primary key doesn't match the argument name // Default: null + * evict_cache?: bool|Param, // Set to true to fetch the entity from the database instead of using the cache, if any // Default: false + * }, + * entity_managers?: array, + * }>, + * }>, + * }, + * connection?: scalar|null|Param, + * class_metadata_factory_name?: scalar|null|Param, // Default: "Doctrine\\ORM\\Mapping\\ClassMetadataFactory" + * default_repository_class?: scalar|null|Param, // Default: "Doctrine\\ORM\\EntityRepository" + * auto_mapping?: scalar|null|Param, // Default: false + * naming_strategy?: scalar|null|Param, // Default: "doctrine.orm.naming_strategy.default" + * quote_strategy?: scalar|null|Param, // Default: "doctrine.orm.quote_strategy.default" + * typed_field_mapper?: scalar|null|Param, // Default: "doctrine.orm.typed_field_mapper.default" + * entity_listener_resolver?: scalar|null|Param, // Default: null + * fetch_mode_subselect_batch_size?: scalar|null|Param, + * repository_factory?: scalar|null|Param, // Default: "doctrine.orm.container_repository_factory" + * schema_ignore_classes?: list, + * report_fields_where_declared?: bool|Param, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.16 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/10455. // Default: true + * validate_xml_mapping?: bool|Param, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.14. See https://github.com/doctrine/orm/pull/6728. // Default: false + * second_level_cache?: array{ + * region_cache_driver?: string|array{ + * type?: scalar|null|Param, // Default: null + * id?: scalar|null|Param, + * pool?: scalar|null|Param, + * }, + * region_lock_lifetime?: scalar|null|Param, // Default: 60 + * log_enabled?: bool|Param, // Default: true + * region_lifetime?: scalar|null|Param, // Default: 3600 + * enabled?: bool|Param, // Default: true + * factory?: scalar|null|Param, + * regions?: array, + * loggers?: array, + * }, + * hydrators?: array, + * mappings?: array, + * dql?: array{ + * string_functions?: array, + * numeric_functions?: array, + * datetime_functions?: array, + * }, + * filters?: array, + * }>, + * identity_generation_preferences?: array, + * }>, + * resolve_target_entities?: array, + * }, + * } + * @psalm-type DoctrineMigrationsConfig = array{ + * enable_service_migrations?: bool|Param, // Whether to enable fetching migrations from the service container. // Default: false + * migrations_paths?: array, + * services?: array, + * factories?: array, + * storage?: array{ // Storage to use for migration status metadata. + * table_storage?: array{ // The default metadata storage, implemented as a table in the database. + * table_name?: scalar|null|Param, // Default: null + * version_column_name?: scalar|null|Param, // Default: null + * version_column_length?: scalar|null|Param, // Default: null + * executed_at_column_name?: scalar|null|Param, // Default: null + * execution_time_column_name?: scalar|null|Param, // Default: null + * }, + * }, + * migrations?: list, + * connection?: scalar|null|Param, // Connection name to use for the migrations database. // Default: null + * em?: scalar|null|Param, // Entity manager name to use for the migrations database (available when doctrine/orm is installed). // Default: null + * all_or_nothing?: scalar|null|Param, // Run all migrations in a transaction. // Default: false + * check_database_platform?: scalar|null|Param, // Adds an extra check in the generated migrations to allow execution only on the same platform as they were initially generated on. // Default: true + * custom_template?: scalar|null|Param, // Custom template path for generated migration classes. // Default: null + * organize_migrations?: scalar|null|Param, // Organize migrations mode. Possible values are: "BY_YEAR", "BY_YEAR_AND_MONTH", false // Default: false + * enable_profiler?: bool|Param, // Whether or not to enable the profiler collector to calculate and visualize migration status. This adds some queries overhead. // Default: false + * transactional?: bool|Param, // Whether or not to wrap migrations in a single transaction. // Default: true + * } + * @psalm-type SecurityConfig = array{ + * access_denied_url?: scalar|null|Param, // Default: null + * session_fixation_strategy?: "none"|"migrate"|"invalidate"|Param, // Default: "migrate" + * hide_user_not_found?: bool|Param, // Deprecated: The "hide_user_not_found" option is deprecated and will be removed in 8.0. Use the "expose_security_errors" option instead. + * expose_security_errors?: \Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::None|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::AccountStatus|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::All|Param, // Default: "none" + * erase_credentials?: bool|Param, // Default: true + * access_decision_manager?: array{ + * strategy?: "affirmative"|"consensus"|"unanimous"|"priority"|Param, + * service?: scalar|null|Param, + * strategy_service?: scalar|null|Param, + * allow_if_all_abstain?: bool|Param, // Default: false + * allow_if_equal_granted_denied?: bool|Param, // Default: true + * }, + * password_hashers?: array, + * hash_algorithm?: scalar|null|Param, // Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. // Default: "sha512" + * key_length?: scalar|null|Param, // Default: 40 + * ignore_case?: bool|Param, // Default: false + * encode_as_base64?: bool|Param, // Default: true + * iterations?: scalar|null|Param, // Default: 5000 + * cost?: int|Param, // Default: null + * memory_cost?: scalar|null|Param, // Default: null + * time_cost?: scalar|null|Param, // Default: null + * id?: scalar|null|Param, + * }>, + * providers?: array, + * }, + * entity?: array{ + * class: scalar|null|Param, // The full entity class name of your user class. + * property?: scalar|null|Param, // Default: null + * manager_name?: scalar|null|Param, // Default: null + * }, + * memory?: array{ + * users?: array, + * }>, + * }, + * ldap?: array{ + * service: scalar|null|Param, + * base_dn: scalar|null|Param, + * search_dn?: scalar|null|Param, // Default: null + * search_password?: scalar|null|Param, // Default: null + * extra_fields?: list, + * default_roles?: list, + * role_fetcher?: scalar|null|Param, // Default: null + * uid_key?: scalar|null|Param, // Default: "sAMAccountName" + * filter?: scalar|null|Param, // Default: "({uid_key}={user_identifier})" + * password_attribute?: scalar|null|Param, // Default: null + * }, + * saml?: array{ + * user_class: scalar|null|Param, + * default_roles?: list, + * }, + * }>, + * firewalls: array, + * security?: bool|Param, // Default: true + * user_checker?: scalar|null|Param, // The UserChecker to use when authenticating users in this firewall. // Default: "security.user_checker" + * request_matcher?: scalar|null|Param, + * access_denied_url?: scalar|null|Param, + * access_denied_handler?: scalar|null|Param, + * entry_point?: scalar|null|Param, // An enabled authenticator name or a service id that implements "Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface". + * provider?: scalar|null|Param, + * stateless?: bool|Param, // Default: false + * lazy?: bool|Param, // Default: false + * context?: scalar|null|Param, + * logout?: array{ + * enable_csrf?: bool|null|Param, // Default: null + * csrf_token_id?: scalar|null|Param, // Default: "logout" + * csrf_parameter?: scalar|null|Param, // Default: "_csrf_token" + * csrf_token_manager?: scalar|null|Param, + * path?: scalar|null|Param, // Default: "/logout" + * target?: scalar|null|Param, // Default: "/" + * invalidate_session?: bool|Param, // Default: true + * clear_site_data?: list<"*"|"cache"|"cookies"|"storage"|"executionContexts"|Param>, + * delete_cookies?: array, + * }, + * switch_user?: array{ + * provider?: scalar|null|Param, + * parameter?: scalar|null|Param, // Default: "_switch_user" + * role?: scalar|null|Param, // Default: "ROLE_ALLOWED_TO_SWITCH" + * target_route?: scalar|null|Param, // Default: null + * }, + * required_badges?: list, + * custom_authenticators?: list, + * login_throttling?: array{ + * limiter?: scalar|null|Param, // A service id implementing "Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface". + * max_attempts?: int|Param, // Default: 5 + * interval?: scalar|null|Param, // Default: "1 minute" + * lock_factory?: scalar|null|Param, // The service ID of the lock factory used by the login rate limiter (or null to disable locking). // Default: null + * cache_pool?: string|Param, // The cache pool to use for storing the limiter state // Default: "cache.rate_limiter" + * storage_service?: string|Param, // The service ID of a custom storage implementation, this precedes any configured "cache_pool" // Default: null + * }, + * two_factor?: array{ + * check_path?: scalar|null|Param, // Default: "/2fa_check" + * post_only?: bool|Param, // Default: true + * auth_form_path?: scalar|null|Param, // Default: "/2fa" + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|null|Param, // Default: "/" + * success_handler?: scalar|null|Param, // Default: null + * failure_handler?: scalar|null|Param, // Default: null + * authentication_required_handler?: scalar|null|Param, // Default: null + * auth_code_parameter_name?: scalar|null|Param, // Default: "_auth_code" + * trusted_parameter_name?: scalar|null|Param, // Default: "_trusted" + * remember_me_sets_trusted?: scalar|null|Param, // Default: false + * multi_factor?: bool|Param, // Default: false + * prepare_on_login?: bool|Param, // Default: false + * prepare_on_access_denied?: bool|Param, // Default: false + * enable_csrf?: scalar|null|Param, // Default: false + * csrf_parameter?: scalar|null|Param, // Default: "_csrf_token" + * csrf_token_id?: scalar|null|Param, // Default: "two_factor" + * csrf_header?: scalar|null|Param, // Default: null + * csrf_token_manager?: scalar|null|Param, // Default: "scheb_two_factor.csrf_token_manager" + * provider?: scalar|null|Param, // Default: null + * }, + * webauthn?: array{ + * user_provider?: scalar|null|Param, // Default: null + * options_storage?: scalar|null|Param, // Deprecated: The child node "options_storage" at path "security.firewalls..webauthn.options_storage" is deprecated. Please use the root option "options_storage" instead. // Default: null + * success_handler?: scalar|null|Param, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultSuccessHandler" + * failure_handler?: scalar|null|Param, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultFailureHandler" + * secured_rp_ids?: array, + * authentication?: bool|array{ + * enabled?: bool|Param, // Default: true + * profile?: scalar|null|Param, // Default: "default" + * options_builder?: scalar|null|Param, // Default: null + * routes?: array{ + * host?: scalar|null|Param, // Default: null + * options_method?: scalar|null|Param, // Default: "POST" + * options_path?: scalar|null|Param, // Default: "/login/options" + * result_method?: scalar|null|Param, // Default: "POST" + * result_path?: scalar|null|Param, // Default: "/login" + * }, + * options_handler?: scalar|null|Param, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultRequestOptionsHandler" + * }, + * registration?: bool|array{ + * enabled?: bool|Param, // Default: false + * profile?: scalar|null|Param, // Default: "default" + * options_builder?: scalar|null|Param, // Default: null + * routes?: array{ + * host?: scalar|null|Param, // Default: null + * options_method?: scalar|null|Param, // Default: "POST" + * options_path?: scalar|null|Param, // Default: "/register/options" + * result_method?: scalar|null|Param, // Default: "POST" + * result_path?: scalar|null|Param, // Default: "/register" + * }, + * options_handler?: scalar|null|Param, // Default: "Webauthn\\Bundle\\Security\\Handler\\DefaultCreationOptionsHandler" + * }, + * }, + * x509?: array{ + * provider?: scalar|null|Param, + * user?: scalar|null|Param, // Default: "SSL_CLIENT_S_DN_Email" + * credentials?: scalar|null|Param, // Default: "SSL_CLIENT_S_DN" + * user_identifier?: scalar|null|Param, // Default: "emailAddress" + * }, + * remote_user?: array{ + * provider?: scalar|null|Param, + * user?: scalar|null|Param, // Default: "REMOTE_USER" + * }, + * saml?: array{ + * provider?: scalar|null|Param, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|null|Param, // Default: "Nbgrp\\OneloginSamlBundle\\Security\\Http\\Authentication\\SamlAuthenticationSuccessHandler" + * failure_handler?: scalar|null|Param, + * check_path?: scalar|null|Param, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|null|Param, // Default: "/login" + * identifier_attribute?: scalar|null|Param, // Default: null + * use_attribute_friendly_name?: bool|Param, // Default: false + * user_factory?: scalar|null|Param, // Default: null + * token_factory?: scalar|null|Param, // Default: null + * persist_user?: bool|Param, // Default: false + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|null|Param, // Default: "/" + * target_path_parameter?: scalar|null|Param, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|null|Param, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|null|Param, // Default: "_failure_path" + * }, + * login_link?: array{ + * check_route: scalar|null|Param, // Route that will validate the login link - e.g. "app_login_link_verify". + * check_post_only?: scalar|null|Param, // If true, only HTTP POST requests to "check_route" will be handled by the authenticator. // Default: false + * signature_properties: list, + * lifetime?: int|Param, // The lifetime of the login link in seconds. // Default: 600 + * max_uses?: int|Param, // Max number of times a login link can be used - null means unlimited within lifetime. // Default: null + * used_link_cache?: scalar|null|Param, // Cache service id used to expired links of max_uses is set. + * success_handler?: scalar|null|Param, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface. + * failure_handler?: scalar|null|Param, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface. + * provider?: scalar|null|Param, // The user provider to load users from. + * secret?: scalar|null|Param, // Default: "%kernel.secret%" + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|null|Param, // Default: "/" + * login_path?: scalar|null|Param, // Default: "/login" + * target_path_parameter?: scalar|null|Param, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|null|Param, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|null|Param, // Default: "_failure_path" + * }, + * form_login?: array{ + * provider?: scalar|null|Param, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|null|Param, + * failure_handler?: scalar|null|Param, + * check_path?: scalar|null|Param, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|null|Param, // Default: "/login" + * username_parameter?: scalar|null|Param, // Default: "_username" + * password_parameter?: scalar|null|Param, // Default: "_password" + * csrf_parameter?: scalar|null|Param, // Default: "_csrf_token" + * csrf_token_id?: scalar|null|Param, // Default: "authenticate" + * enable_csrf?: bool|Param, // Default: false + * post_only?: bool|Param, // Default: true + * form_only?: bool|Param, // Default: false + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|null|Param, // Default: "/" + * target_path_parameter?: scalar|null|Param, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|null|Param, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|null|Param, // Default: "_failure_path" + * }, + * form_login_ldap?: array{ + * provider?: scalar|null|Param, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|null|Param, + * failure_handler?: scalar|null|Param, + * check_path?: scalar|null|Param, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|null|Param, // Default: "/login" + * username_parameter?: scalar|null|Param, // Default: "_username" + * password_parameter?: scalar|null|Param, // Default: "_password" + * csrf_parameter?: scalar|null|Param, // Default: "_csrf_token" + * csrf_token_id?: scalar|null|Param, // Default: "authenticate" + * enable_csrf?: bool|Param, // Default: false + * post_only?: bool|Param, // Default: true + * form_only?: bool|Param, // Default: false + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|null|Param, // Default: "/" + * target_path_parameter?: scalar|null|Param, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|null|Param, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|null|Param, // Default: "_failure_path" + * service?: scalar|null|Param, // Default: "ldap" + * dn_string?: scalar|null|Param, // Default: "{user_identifier}" + * query_string?: scalar|null|Param, + * search_dn?: scalar|null|Param, // Default: "" + * search_password?: scalar|null|Param, // Default: "" + * }, + * json_login?: array{ + * provider?: scalar|null|Param, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|null|Param, + * failure_handler?: scalar|null|Param, + * check_path?: scalar|null|Param, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|null|Param, // Default: "/login" + * username_path?: scalar|null|Param, // Default: "username" + * password_path?: scalar|null|Param, // Default: "password" + * }, + * json_login_ldap?: array{ + * provider?: scalar|null|Param, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|null|Param, + * failure_handler?: scalar|null|Param, + * check_path?: scalar|null|Param, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|null|Param, // Default: "/login" + * username_path?: scalar|null|Param, // Default: "username" + * password_path?: scalar|null|Param, // Default: "password" + * service?: scalar|null|Param, // Default: "ldap" + * dn_string?: scalar|null|Param, // Default: "{user_identifier}" + * query_string?: scalar|null|Param, + * search_dn?: scalar|null|Param, // Default: "" + * search_password?: scalar|null|Param, // Default: "" + * }, + * access_token?: array{ + * provider?: scalar|null|Param, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|null|Param, + * failure_handler?: scalar|null|Param, + * realm?: scalar|null|Param, // Default: null + * token_extractors?: list, + * token_handler: string|array{ + * id?: scalar|null|Param, + * oidc_user_info?: string|array{ + * base_uri: scalar|null|Param, // Base URI of the userinfo endpoint on the OIDC server, or the OIDC server URI to use the discovery (require "discovery" to be configured). + * discovery?: array{ // Enable the OIDC discovery. + * cache?: array{ + * id: scalar|null|Param, // Cache service id to use to cache the OIDC discovery configuration. + * }, + * }, + * claim?: scalar|null|Param, // Claim which contains the user identifier (e.g. sub, email, etc.). // Default: "sub" + * client?: scalar|null|Param, // HttpClient service id to use to call the OIDC server. + * }, + * oidc?: array{ + * discovery?: array{ // Enable the OIDC discovery. + * base_uri: list, + * cache?: array{ + * id: scalar|null|Param, // Cache service id to use to cache the OIDC discovery configuration. + * }, + * }, + * claim?: scalar|null|Param, // Claim which contains the user identifier (e.g.: sub, email..). // Default: "sub" + * audience: scalar|null|Param, // Audience set in the token, for validation purpose. + * issuers: list, + * algorithm?: array, + * algorithms: list, + * key?: scalar|null|Param, // Deprecated: The "key" option is deprecated and will be removed in 8.0. Use the "keyset" option instead. // JSON-encoded JWK used to sign the token (must contain a "kty" key). + * keyset?: scalar|null|Param, // JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys). + * encryption?: bool|array{ + * enabled?: bool|Param, // Default: false + * enforce?: bool|Param, // When enabled, the token shall be encrypted. // Default: false + * algorithms: list, + * keyset: scalar|null|Param, // JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys). + * }, + * }, + * cas?: array{ + * validation_url: scalar|null|Param, // CAS server validation URL + * prefix?: scalar|null|Param, // CAS prefix // Default: "cas" + * http_client?: scalar|null|Param, // HTTP Client service // Default: null + * }, + * oauth2?: scalar|null|Param, + * }, + * }, + * http_basic?: array{ + * provider?: scalar|null|Param, + * realm?: scalar|null|Param, // Default: "Secured Area" + * }, + * http_basic_ldap?: array{ + * provider?: scalar|null|Param, + * realm?: scalar|null|Param, // Default: "Secured Area" + * service?: scalar|null|Param, // Default: "ldap" + * dn_string?: scalar|null|Param, // Default: "{user_identifier}" + * query_string?: scalar|null|Param, + * search_dn?: scalar|null|Param, // Default: "" + * search_password?: scalar|null|Param, // Default: "" + * }, + * remember_me?: array{ + * secret?: scalar|null|Param, // Default: "%kernel.secret%" + * service?: scalar|null|Param, + * user_providers?: list, + * catch_exceptions?: bool|Param, // Default: true + * signature_properties?: list, + * token_provider?: string|array{ + * service?: scalar|null|Param, // The service ID of a custom remember-me token provider. + * doctrine?: bool|array{ + * enabled?: bool|Param, // Default: false + * connection?: scalar|null|Param, // Default: null + * }, + * }, + * token_verifier?: scalar|null|Param, // The service ID of a custom rememberme token verifier. + * name?: scalar|null|Param, // Default: "REMEMBERME" + * lifetime?: int|Param, // Default: 31536000 + * path?: scalar|null|Param, // Default: "/" + * domain?: scalar|null|Param, // Default: null + * secure?: true|false|"auto"|Param, // Default: null + * httponly?: bool|Param, // Default: true + * samesite?: null|"lax"|"strict"|"none"|Param, // Default: "lax" + * always_remember_me?: bool|Param, // Default: false + * remember_me_parameter?: scalar|null|Param, // Default: "_remember_me" + * }, + * }>, + * access_control?: list, + * attributes?: array, + * route?: scalar|null|Param, // Default: null + * methods?: list, + * allow_if?: scalar|null|Param, // Default: null + * roles?: list, + * }>, + * role_hierarchy?: array>, + * } + * @psalm-type TwigConfig = array{ + * form_themes?: list, + * globals?: array, + * autoescape_service?: scalar|null|Param, // Default: null + * autoescape_service_method?: scalar|null|Param, // Default: null + * base_template_class?: scalar|null|Param, // Deprecated: The child node "base_template_class" at path "twig.base_template_class" is deprecated. + * cache?: scalar|null|Param, // Default: true + * charset?: scalar|null|Param, // Default: "%kernel.charset%" + * debug?: bool|Param, // Default: "%kernel.debug%" + * strict_variables?: bool|Param, // Default: "%kernel.debug%" + * auto_reload?: scalar|null|Param, + * optimizations?: int|Param, + * default_path?: scalar|null|Param, // The default path used to load templates. // Default: "%kernel.project_dir%/templates" + * file_name_pattern?: list, + * paths?: array, + * date?: array{ // The default format options used by the date filter. + * format?: scalar|null|Param, // Default: "F j, Y H:i" + * interval_format?: scalar|null|Param, // Default: "%d days" + * timezone?: scalar|null|Param, // The timezone used when formatting dates, when set to null, the timezone returned by date_default_timezone_get() is used. // Default: null + * }, + * number_format?: array{ // The default format options for the number_format filter. + * decimals?: int|Param, // Default: 0 + * decimal_point?: scalar|null|Param, // Default: "." + * thousands_separator?: scalar|null|Param, // Default: "," + * }, + * mailer?: array{ + * html_to_text_converter?: scalar|null|Param, // A service implementing the "Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface". // Default: null + * }, + * } + * @psalm-type WebProfilerConfig = array{ + * toolbar?: bool|array{ // Profiler toolbar configuration + * enabled?: bool|Param, // Default: false + * ajax_replace?: bool|Param, // Replace toolbar on AJAX requests // Default: false + * }, + * intercept_redirects?: bool|Param, // Default: false + * excluded_ajax_paths?: scalar|null|Param, // Default: "^/((index|app(_[\\w]+)?)\\.php/)?_wdt" + * } + * @psalm-type MonologConfig = array{ + * use_microseconds?: scalar|null|Param, // Default: true + * channels?: list, + * handlers?: array, + * excluded_http_codes?: list, + * }>, + * accepted_levels?: list, + * min_level?: scalar|null|Param, // Default: "DEBUG" + * max_level?: scalar|null|Param, // Default: "EMERGENCY" + * buffer_size?: scalar|null|Param, // Default: 0 + * flush_on_overflow?: bool|Param, // Default: false + * handler?: scalar|null|Param, + * url?: scalar|null|Param, + * exchange?: scalar|null|Param, + * exchange_name?: scalar|null|Param, // Default: "log" + * room?: scalar|null|Param, + * message_format?: scalar|null|Param, // Default: "text" + * api_version?: scalar|null|Param, // Default: null + * channel?: scalar|null|Param, // Default: null + * bot_name?: scalar|null|Param, // Default: "Monolog" + * use_attachment?: scalar|null|Param, // Default: true + * use_short_attachment?: scalar|null|Param, // Default: false + * include_extra?: scalar|null|Param, // Default: false + * icon_emoji?: scalar|null|Param, // Default: null + * webhook_url?: scalar|null|Param, + * exclude_fields?: list, + * team?: scalar|null|Param, + * notify?: scalar|null|Param, // Default: false + * nickname?: scalar|null|Param, // Default: "Monolog" + * token?: scalar|null|Param, + * region?: scalar|null|Param, + * source?: scalar|null|Param, + * use_ssl?: bool|Param, // Default: true + * user?: mixed, + * title?: scalar|null|Param, // Default: null + * host?: scalar|null|Param, // Default: null + * port?: scalar|null|Param, // Default: 514 + * config?: list, + * members?: list, + * connection_string?: scalar|null|Param, + * timeout?: scalar|null|Param, + * time?: scalar|null|Param, // Default: 60 + * deduplication_level?: scalar|null|Param, // Default: 400 + * store?: scalar|null|Param, // Default: null + * connection_timeout?: scalar|null|Param, + * persistent?: bool|Param, + * dsn?: scalar|null|Param, + * hub_id?: scalar|null|Param, // Default: null + * client_id?: scalar|null|Param, // Default: null + * auto_log_stacks?: scalar|null|Param, // Default: false + * release?: scalar|null|Param, // Default: null + * environment?: scalar|null|Param, // Default: null + * message_type?: scalar|null|Param, // Default: 0 + * parse_mode?: scalar|null|Param, // Default: null + * disable_webpage_preview?: bool|null|Param, // Default: null + * disable_notification?: bool|null|Param, // Default: null + * split_long_messages?: bool|Param, // Default: false + * delay_between_messages?: bool|Param, // Default: false + * topic?: int|Param, // Default: null + * factor?: int|Param, // Default: 1 + * tags?: list, + * console_formater_options?: mixed, // Deprecated: "monolog.handlers..console_formater_options.console_formater_options" is deprecated, use "monolog.handlers..console_formater_options.console_formatter_options" instead. + * console_formatter_options?: mixed, // Default: [] + * formatter?: scalar|null|Param, + * nested?: bool|Param, // Default: false + * publisher?: string|array{ + * id?: scalar|null|Param, + * hostname?: scalar|null|Param, + * port?: scalar|null|Param, // Default: 12201 + * chunk_size?: scalar|null|Param, // Default: 1420 + * encoder?: "json"|"compressed_json"|Param, + * }, + * mongo?: string|array{ + * id?: scalar|null|Param, + * host?: scalar|null|Param, + * port?: scalar|null|Param, // Default: 27017 + * user?: scalar|null|Param, + * pass?: scalar|null|Param, + * database?: scalar|null|Param, // Default: "monolog" + * collection?: scalar|null|Param, // Default: "logs" + * }, + * mongodb?: string|array{ + * id?: scalar|null|Param, // ID of a MongoDB\Client service + * uri?: scalar|null|Param, + * username?: scalar|null|Param, + * password?: scalar|null|Param, + * database?: scalar|null|Param, // Default: "monolog" + * collection?: scalar|null|Param, // Default: "logs" + * }, + * elasticsearch?: string|array{ + * id?: scalar|null|Param, + * hosts?: list, + * host?: scalar|null|Param, + * port?: scalar|null|Param, // Default: 9200 + * transport?: scalar|null|Param, // Default: "Http" + * user?: scalar|null|Param, // Default: null + * password?: scalar|null|Param, // Default: null + * }, + * index?: scalar|null|Param, // Default: "monolog" + * document_type?: scalar|null|Param, // Default: "logs" + * ignore_error?: scalar|null|Param, // Default: false + * redis?: string|array{ + * id?: scalar|null|Param, + * host?: scalar|null|Param, + * password?: scalar|null|Param, // Default: null + * port?: scalar|null|Param, // Default: 6379 + * database?: scalar|null|Param, // Default: 0 + * key_name?: scalar|null|Param, // Default: "monolog_redis" + * }, + * predis?: string|array{ + * id?: scalar|null|Param, + * host?: scalar|null|Param, + * }, + * from_email?: scalar|null|Param, + * to_email?: list, + * subject?: scalar|null|Param, + * content_type?: scalar|null|Param, // Default: null + * headers?: list, + * mailer?: scalar|null|Param, // Default: null + * email_prototype?: string|array{ + * id: scalar|null|Param, + * method?: scalar|null|Param, // Default: null + * }, + * lazy?: bool|Param, // Default: true + * verbosity_levels?: array{ + * VERBOSITY_QUIET?: scalar|null|Param, // Default: "ERROR" + * VERBOSITY_NORMAL?: scalar|null|Param, // Default: "WARNING" + * VERBOSITY_VERBOSE?: scalar|null|Param, // Default: "NOTICE" + * VERBOSITY_VERY_VERBOSE?: scalar|null|Param, // Default: "INFO" + * VERBOSITY_DEBUG?: scalar|null|Param, // Default: "DEBUG" + * }, + * channels?: string|array{ + * type?: scalar|null|Param, + * elements?: list, + * }, + * }>, + * } + * @psalm-type DebugConfig = array{ + * max_items?: int|Param, // Max number of displayed items past the first level, -1 means no limit. // Default: 2500 + * min_depth?: int|Param, // Minimum tree depth to clone all the items, 1 is default. // Default: 1 + * max_string_length?: int|Param, // Max length of displayed strings, -1 means no limit. // Default: -1 + * dump_destination?: scalar|null|Param, // A stream URL where dumps should be written to. // Default: null + * theme?: "dark"|"light"|Param, // Changes the color of the dump() output when rendered directly on the templating. "dark" (default) or "light". // Default: "dark" + * } + * @psalm-type MakerConfig = array{ + * root_namespace?: scalar|null|Param, // Default: "App" + * generate_final_classes?: bool|Param, // Default: true + * generate_final_entities?: bool|Param, // Default: false + * } + * @psalm-type WebpackEncoreConfig = array{ + * output_path: scalar|null|Param, // The path where Encore is building the assets - i.e. Encore.setOutputPath() + * crossorigin?: false|"anonymous"|"use-credentials"|Param, // crossorigin value when Encore.enableIntegrityHashes() is used, can be false (default), anonymous or use-credentials // Default: false + * preload?: bool|Param, // preload all rendered script and link tags automatically via the http2 Link header. // Default: false + * cache?: bool|Param, // Enable caching of the entry point file(s) // Default: false + * strict_mode?: bool|Param, // Throw an exception if the entrypoints.json file is missing or an entry is missing from the data // Default: true + * builds?: array, + * script_attributes?: array, + * link_attributes?: array, + * } + * @psalm-type DatatablesConfig = array{ + * language_from_cdn?: bool|Param, // Load i18n data from DataTables CDN or locally // Default: true + * persist_state?: "none"|"query"|"fragment"|"local"|"session"|Param, // Where to persist the current table state automatically // Default: "fragment" + * method?: "GET"|"POST"|Param, // Default HTTP method to be used for callbacks // Default: "POST" + * options?: array, + * renderer?: scalar|null|Param, // Default service used to render templates, built-in TwigRenderer uses global Twig environment // Default: "Omines\\DataTablesBundle\\Twig\\TwigRenderer" + * template?: scalar|null|Param, // Default template to be used for DataTables HTML // Default: "@DataTables/datatable_html.html.twig" + * template_parameters?: array{ // Default parameters to be passed to the template + * className?: scalar|null|Param, // Default class attribute to apply to the root table elements // Default: "table table-bordered" + * columnFilter?: "thead"|"tfoot"|"both"|null|Param, // If and where to enable the DataTables Filter module // Default: null + * ... + * }, + * translation_domain?: scalar|null|Param, // Default translation domain to be used // Default: "messages" + * } + * @psalm-type LiipImagineConfig = array{ + * resolvers?: array, + * get_options?: array, + * put_options?: array, + * proxies?: array, + * }, + * flysystem?: array{ + * filesystem_service: scalar|null|Param, + * cache_prefix?: scalar|null|Param, // Default: "" + * root_url: scalar|null|Param, + * visibility?: "public"|"private"|"noPredefinedVisibility"|Param, // Default: "public" + * }, + * }>, + * loaders?: array, + * allow_unresolvable_data_roots?: bool|Param, // Default: false + * bundle_resources?: array{ + * enabled?: bool|Param, // Default: false + * access_control_type?: "blacklist"|"whitelist"|Param, // Sets the access control method applied to bundle names in "access_control_list" into a blacklist or whitelist. // Default: "blacklist" + * access_control_list?: list, + * }, + * }, + * flysystem?: array{ + * filesystem_service: scalar|null|Param, + * }, + * chain?: array{ + * loaders: list, + * }, + * }>, + * driver?: scalar|null|Param, // Default: "gd" + * cache?: scalar|null|Param, // Default: "default" + * cache_base_path?: scalar|null|Param, // Default: "" + * data_loader?: scalar|null|Param, // Default: "default" + * default_image?: scalar|null|Param, // Default: null + * default_filter_set_settings?: array{ + * quality?: scalar|null|Param, // Default: 100 + * jpeg_quality?: scalar|null|Param, // Default: null + * png_compression_level?: scalar|null|Param, // Default: null + * png_compression_filter?: scalar|null|Param, // Default: null + * format?: scalar|null|Param, // Default: null + * animated?: bool|Param, // Default: false + * cache?: scalar|null|Param, // Default: null + * data_loader?: scalar|null|Param, // Default: null + * default_image?: scalar|null|Param, // Default: null + * filters?: array>, + * post_processors?: array>, + * }, + * controller?: array{ + * filter_action?: scalar|null|Param, // Default: "Liip\\ImagineBundle\\Controller\\ImagineController::filterAction" + * filter_runtime_action?: scalar|null|Param, // Default: "Liip\\ImagineBundle\\Controller\\ImagineController::filterRuntimeAction" + * redirect_response_code?: int|Param, // Default: 302 + * }, + * filter_sets?: array>, + * post_processors?: array>, + * }>, + * twig?: array{ + * mode?: "none"|"lazy"|"legacy"|Param, // Twig mode: none/lazy/legacy (default) // Default: "legacy" + * assets_version?: scalar|null|Param, // Default: null + * }, + * enqueue?: bool|Param, // Enables integration with enqueue if set true. Allows resolve image caches in background by sending messages to MQ. // Default: false + * messenger?: bool|array{ // Enables integration with symfony/messenger if set true. Warmup image caches in background by sending messages to MQ. + * enabled?: bool|Param, // Default: false + * }, + * templating?: bool|Param, // Enables integration with symfony/templating component // Default: true + * webp?: array{ + * generate?: bool|Param, // Default: false + * quality?: int|Param, // Default: 100 + * cache?: scalar|null|Param, // Default: null + * data_loader?: scalar|null|Param, // Default: null + * post_processors?: array>, + * }, + * } + * @psalm-type DamaDoctrineTestConfig = array{ + * enable_static_connection?: mixed, // Default: true + * enable_static_meta_data_cache?: bool|Param, // Default: true + * enable_static_query_cache?: bool|Param, // Default: true + * connection_keys?: list, + * } + * @psalm-type TwigExtraConfig = array{ + * cache?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * html?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * markdown?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * intl?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * cssinliner?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * inky?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * string?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * commonmark?: array{ + * renderer?: array{ // Array of options for rendering HTML. + * block_separator?: scalar|null|Param, + * inner_separator?: scalar|null|Param, + * soft_break?: scalar|null|Param, + * }, + * html_input?: "strip"|"allow"|"escape"|Param, // How to handle HTML input. + * allow_unsafe_links?: bool|Param, // Remove risky link and image URLs by setting this to false. // Default: true + * max_nesting_level?: int|Param, // The maximum nesting level for blocks. // Default: 9223372036854775807 + * max_delimiters_per_line?: int|Param, // The maximum number of strong/emphasis delimiters per line. // Default: 9223372036854775807 + * slug_normalizer?: array{ // Array of options for configuring how URL-safe slugs are created. + * instance?: mixed, + * max_length?: int|Param, // Default: 255 + * unique?: mixed, + * }, + * commonmark?: array{ // Array of options for configuring the CommonMark core extension. + * enable_em?: bool|Param, // Default: true + * enable_strong?: bool|Param, // Default: true + * use_asterisk?: bool|Param, // Default: true + * use_underscore?: bool|Param, // Default: true + * unordered_list_markers?: list, + * }, + * ... + * }, + * } + * @psalm-type GregwarCaptchaConfig = array{ + * length?: scalar|null|Param, // Default: 5 + * width?: scalar|null|Param, // Default: 130 + * height?: scalar|null|Param, // Default: 50 + * font?: scalar|null|Param, // Default: "C:\\Users\\mail\\Documents\\PHP\\Part-DB-server\\vendor\\gregwar\\captcha-bundle\\DependencyInjection/../Generator/Font/captcha.ttf" + * keep_value?: scalar|null|Param, // Default: false + * charset?: scalar|null|Param, // Default: "abcdefhjkmnprstuvwxyz23456789" + * as_file?: scalar|null|Param, // Default: false + * as_url?: scalar|null|Param, // Default: false + * reload?: scalar|null|Param, // Default: false + * image_folder?: scalar|null|Param, // Default: "captcha" + * web_path?: scalar|null|Param, // Default: "%kernel.project_dir%/public" + * gc_freq?: scalar|null|Param, // Default: 100 + * expiration?: scalar|null|Param, // Default: 60 + * quality?: scalar|null|Param, // Default: 50 + * invalid_message?: scalar|null|Param, // Default: "Bad code value" + * bypass_code?: scalar|null|Param, // Default: null + * whitelist_key?: scalar|null|Param, // Default: "captcha_whitelist_key" + * humanity?: scalar|null|Param, // Default: 0 + * distortion?: scalar|null|Param, // Default: true + * max_front_lines?: scalar|null|Param, // Default: null + * max_behind_lines?: scalar|null|Param, // Default: null + * interpolation?: scalar|null|Param, // Default: true + * text_color?: list, + * background_color?: list, + * background_images?: list, + * disabled?: scalar|null|Param, // Default: false + * ignore_all_effects?: scalar|null|Param, // Default: false + * session_key?: scalar|null|Param, // Default: "captcha" + * } + * @psalm-type FlorianvSwapConfig = array{ + * cache?: array{ + * ttl?: int|Param, // Default: 3600 + * type?: scalar|null|Param, // A cache type or service id // Default: null + * }, + * providers?: array{ + * apilayer_fixer?: array{ + * priority?: int|Param, // Default: 0 + * api_key: scalar|null|Param, + * }, + * apilayer_currency_data?: array{ + * priority?: int|Param, // Default: 0 + * api_key: scalar|null|Param, + * }, + * apilayer_exchange_rates_data?: array{ + * priority?: int|Param, // Default: 0 + * api_key: scalar|null|Param, + * }, + * abstract_api?: array{ + * priority?: int|Param, // Default: 0 + * api_key: scalar|null|Param, + * }, + * fixer?: array{ + * priority?: int|Param, // Default: 0 + * access_key: scalar|null|Param, + * enterprise?: bool|Param, // Default: false + * }, + * cryptonator?: array{ + * priority?: int|Param, // Default: 0 + * }, + * exchange_rates_api?: array{ + * priority?: int|Param, // Default: 0 + * access_key: scalar|null|Param, + * enterprise?: bool|Param, // Default: false + * }, + * webservicex?: array{ + * priority?: int|Param, // Default: 0 + * }, + * central_bank_of_czech_republic?: array{ + * priority?: int|Param, // Default: 0 + * }, + * central_bank_of_republic_turkey?: array{ + * priority?: int|Param, // Default: 0 + * }, + * european_central_bank?: array{ + * priority?: int|Param, // Default: 0 + * }, + * national_bank_of_romania?: array{ + * priority?: int|Param, // Default: 0 + * }, + * russian_central_bank?: array{ + * priority?: int|Param, // Default: 0 + * }, + * frankfurter?: array{ + * priority?: int|Param, // Default: 0 + * }, + * fawazahmed_currency_api?: array{ + * priority?: int|Param, // Default: 0 + * }, + * bulgarian_national_bank?: array{ + * priority?: int|Param, // Default: 0 + * }, + * national_bank_of_ukraine?: array{ + * priority?: int|Param, // Default: 0 + * }, + * currency_data_feed?: array{ + * priority?: int|Param, // Default: 0 + * api_key: scalar|null|Param, + * }, + * currency_layer?: array{ + * priority?: int|Param, // Default: 0 + * access_key: scalar|null|Param, + * enterprise?: bool|Param, // Default: false + * }, + * forge?: array{ + * priority?: int|Param, // Default: 0 + * api_key: scalar|null|Param, + * }, + * open_exchange_rates?: array{ + * priority?: int|Param, // Default: 0 + * app_id: scalar|null|Param, + * enterprise?: bool|Param, // Default: false + * }, + * xignite?: array{ + * priority?: int|Param, // Default: 0 + * token: scalar|null|Param, + * }, + * xchangeapi?: array{ + * priority?: int|Param, // Default: 0 + * api_key: scalar|null|Param, + * }, + * currency_converter?: array{ + * priority?: int|Param, // Default: 0 + * access_key: scalar|null|Param, + * enterprise?: bool|Param, // Default: false + * }, + * array?: array{ + * priority?: int|Param, // Default: 0 + * latestRates: mixed, + * historicalRates?: mixed, + * }, + * }, + * } + * @psalm-type NelmioSecurityConfig = array{ + * signed_cookie?: array{ + * names?: list, + * secret?: scalar|null|Param, // Default: "%kernel.secret%" + * hash_algo?: scalar|null|Param, + * legacy_hash_algo?: scalar|null|Param, // Fallback algorithm to allow for frictionless hash algorithm upgrades. Use with caution and as a temporary measure as it allows for downgrade attacks. // Default: null + * separator?: scalar|null|Param, // Default: "." + * }, + * clickjacking?: array{ + * hosts?: list, + * paths?: array, + * content_types?: list, + * }, + * external_redirects?: array{ + * abort?: bool|Param, // Default: false + * override?: scalar|null|Param, // Default: null + * forward_as?: scalar|null|Param, // Default: null + * log?: bool|Param, // Default: false + * allow_list?: list, + * }, + * flexible_ssl?: bool|array{ + * enabled?: bool|Param, // Default: false + * cookie_name?: scalar|null|Param, // Default: "auth" + * unsecured_logout?: bool|Param, // Default: false + * }, + * forced_ssl?: bool|array{ + * enabled?: bool|Param, // Default: false + * hsts_max_age?: scalar|null|Param, // Default: null + * hsts_subdomains?: bool|Param, // Default: false + * hsts_preload?: bool|Param, // Default: false + * allow_list?: list, + * hosts?: list, + * redirect_status_code?: scalar|null|Param, // Default: 302 + * }, + * content_type?: array{ + * nosniff?: bool|Param, // Default: false + * }, + * xss_protection?: array{ // Deprecated: The "xss_protection" option is deprecated, use Content Security Policy without allowing "unsafe-inline" scripts instead. + * enabled?: bool|Param, // Default: false + * mode_block?: bool|Param, // Default: false + * report_uri?: scalar|null|Param, // Default: null + * }, + * csp?: bool|array{ + * enabled?: bool|Param, // Default: true + * request_matcher?: scalar|null|Param, // Default: null + * hosts?: list, + * content_types?: list, + * report_endpoint?: array{ + * log_channel?: scalar|null|Param, // Default: null + * log_formatter?: scalar|null|Param, // Default: "nelmio_security.csp_report.log_formatter" + * log_level?: "alert"|"critical"|"debug"|"emergency"|"error"|"info"|"notice"|"warning"|Param, // Default: "notice" + * filters?: array{ + * domains?: bool|Param, // Default: true + * schemes?: bool|Param, // Default: true + * browser_bugs?: bool|Param, // Default: true + * injected_scripts?: bool|Param, // Default: true + * }, + * dismiss?: list>, + * }, + * compat_headers?: bool|Param, // Default: true + * report_logger_service?: scalar|null|Param, // Default: "logger" + * hash?: array{ + * algorithm?: "sha256"|"sha384"|"sha512"|Param, // The algorithm to use for hashes // Default: "sha256" + * }, + * report?: array{ + * level1_fallback?: bool|Param, // Provides CSP Level 1 fallback when using hash or nonce (CSP level 2) by adding 'unsafe-inline' source. See https://www.w3.org/TR/CSP2/#directive-script-src and https://www.w3.org/TR/CSP2/#directive-style-src // Default: true + * browser_adaptive?: bool|array{ // Do not send directives that browser do not support + * enabled?: bool|Param, // Default: false + * parser?: scalar|null|Param, // Default: "nelmio_security.ua_parser.ua_php" + * }, + * default-src?: list, + * base-uri?: list, + * block-all-mixed-content?: bool|Param, // Default: false + * child-src?: list, + * connect-src?: list, + * font-src?: list, + * form-action?: list, + * frame-ancestors?: list, + * frame-src?: list, + * img-src?: list, + * manifest-src?: list, + * media-src?: list, + * object-src?: list, + * plugin-types?: list, + * script-src?: list, + * style-src?: list, + * upgrade-insecure-requests?: bool|Param, // Default: false + * report-uri?: list, + * worker-src?: list, + * prefetch-src?: list, + * report-to?: scalar|null|Param, + * }, + * enforce?: array{ + * level1_fallback?: bool|Param, // Provides CSP Level 1 fallback when using hash or nonce (CSP level 2) by adding 'unsafe-inline' source. See https://www.w3.org/TR/CSP2/#directive-script-src and https://www.w3.org/TR/CSP2/#directive-style-src // Default: true + * browser_adaptive?: bool|array{ // Do not send directives that browser do not support + * enabled?: bool|Param, // Default: false + * parser?: scalar|null|Param, // Default: "nelmio_security.ua_parser.ua_php" + * }, + * default-src?: list, + * base-uri?: list, + * block-all-mixed-content?: bool|Param, // Default: false + * child-src?: list, + * connect-src?: list, + * font-src?: list, + * form-action?: list, + * frame-ancestors?: list, + * frame-src?: list, + * img-src?: list, + * manifest-src?: list, + * media-src?: list, + * object-src?: list, + * plugin-types?: list, + * script-src?: list, + * style-src?: list, + * upgrade-insecure-requests?: bool|Param, // Default: false + * report-uri?: list, + * worker-src?: list, + * prefetch-src?: list, + * report-to?: scalar|null|Param, + * }, + * }, + * referrer_policy?: bool|array{ + * enabled?: bool|Param, // Default: false + * policies?: list, + * }, + * permissions_policy?: bool|array{ + * enabled?: bool|Param, // Default: false + * policies?: array{ + * accelerometer?: mixed, // Default: null + * ambient_light_sensor?: mixed, // Default: null + * attribution_reporting?: mixed, // Default: null + * autoplay?: mixed, // Default: null + * bluetooth?: mixed, // Default: null + * browsing_topics?: mixed, // Default: null + * camera?: mixed, // Default: null + * captured_surface_control?: mixed, // Default: null + * compute_pressure?: mixed, // Default: null + * cross_origin_isolated?: mixed, // Default: null + * deferred_fetch?: mixed, // Default: null + * deferred_fetch_minimal?: mixed, // Default: null + * display_capture?: mixed, // Default: null + * encrypted_media?: mixed, // Default: null + * fullscreen?: mixed, // Default: null + * gamepad?: mixed, // Default: null + * geolocation?: mixed, // Default: null + * gyroscope?: mixed, // Default: null + * hid?: mixed, // Default: null + * identity_credentials_get?: mixed, // Default: null + * idle_detection?: mixed, // Default: null + * interest_cohort?: mixed, // Default: null + * language_detector?: mixed, // Default: null + * local_fonts?: mixed, // Default: null + * magnetometer?: mixed, // Default: null + * microphone?: mixed, // Default: null + * midi?: mixed, // Default: null + * otp_credentials?: mixed, // Default: null + * payment?: mixed, // Default: null + * picture_in_picture?: mixed, // Default: null + * publickey_credentials_create?: mixed, // Default: null + * publickey_credentials_get?: mixed, // Default: null + * screen_wake_lock?: mixed, // Default: null + * serial?: mixed, // Default: null + * speaker_selection?: mixed, // Default: null + * storage_access?: mixed, // Default: null + * summarizer?: mixed, // Default: null + * translator?: mixed, // Default: null + * usb?: mixed, // Default: null + * web_share?: mixed, // Default: null + * window_management?: mixed, // Default: null + * xr_spatial_tracking?: mixed, // Default: null + * }, + * }, + * } + * @psalm-type TurboConfig = array{ + * broadcast?: bool|array{ + * enabled?: bool|Param, // Default: true + * entity_template_prefixes?: list, + * doctrine_orm?: bool|array{ // Enable the Doctrine ORM integration + * enabled?: bool|Param, // Default: true + * }, + * }, + * default_transport?: scalar|null|Param, // Default: "default" + * } + * @psalm-type TfaWebauthnConfig = array{ + * enabled?: scalar|null|Param, // Default: false + * timeout?: int|Param, // Default: 60000 + * rpID?: scalar|null|Param, // Default: null + * rpName?: scalar|null|Param, // Default: "Webauthn Application" + * rpIcon?: scalar|null|Param, // Default: null + * template?: scalar|null|Param, // Default: "@TFAWebauthn/Authentication/form.html.twig" + * U2FAppID?: scalar|null|Param, // Default: null + * } + * @psalm-type SchebTwoFactorConfig = array{ + * persister?: scalar|null|Param, // Default: "scheb_two_factor.persister.doctrine" + * model_manager_name?: scalar|null|Param, // Default: null + * security_tokens?: list, + * ip_whitelist?: list, + * ip_whitelist_provider?: scalar|null|Param, // Default: "scheb_two_factor.default_ip_whitelist_provider" + * two_factor_token_factory?: scalar|null|Param, // Default: "scheb_two_factor.default_token_factory" + * two_factor_provider_decider?: scalar|null|Param, // Default: "scheb_two_factor.default_provider_decider" + * two_factor_condition?: scalar|null|Param, // Default: null + * code_reuse_cache?: scalar|null|Param, // Default: null + * code_reuse_cache_duration?: int|Param, // Default: 60 + * code_reuse_default_handler?: scalar|null|Param, // Default: null + * trusted_device?: bool|array{ + * enabled?: scalar|null|Param, // Default: false + * manager?: scalar|null|Param, // Default: "scheb_two_factor.default_trusted_device_manager" + * lifetime?: int|Param, // Default: 5184000 + * extend_lifetime?: bool|Param, // Default: false + * key?: scalar|null|Param, // Default: null + * cookie_name?: scalar|null|Param, // Default: "trusted_device" + * cookie_secure?: true|false|"auto"|Param, // Default: "auto" + * cookie_domain?: scalar|null|Param, // Default: null + * cookie_path?: scalar|null|Param, // Default: "/" + * cookie_same_site?: scalar|null|Param, // Default: "lax" + * }, + * backup_codes?: bool|array{ + * enabled?: scalar|null|Param, // Default: false + * manager?: scalar|null|Param, // Default: "scheb_two_factor.default_backup_code_manager" + * }, + * google?: bool|array{ + * enabled?: scalar|null|Param, // Default: false + * form_renderer?: scalar|null|Param, // Default: null + * issuer?: scalar|null|Param, // Default: null + * server_name?: scalar|null|Param, // Default: null + * template?: scalar|null|Param, // Default: "@SchebTwoFactor/Authentication/form.html.twig" + * digits?: int|Param, // Default: 6 + * leeway?: int|Param, // Default: 0 + * }, + * } + * @psalm-type WebauthnConfig = array{ + * fake_credential_generator?: scalar|null|Param, // A service that implements the FakeCredentialGenerator to generate fake credentials for preventing username enumeration. // Default: "Webauthn\\SimpleFakeCredentialGenerator" + * clock?: scalar|null|Param, // PSR-20 Clock service. // Default: "webauthn.clock.default" + * options_storage?: scalar|null|Param, // Service responsible of the options/user entity storage during the ceremony // Default: "Webauthn\\Bundle\\Security\\Storage\\SessionStorage" + * event_dispatcher?: scalar|null|Param, // PSR-14 Event Dispatcher service. // Default: "Psr\\EventDispatcher\\EventDispatcherInterface" + * http_client?: scalar|null|Param, // A Symfony HTTP client. // Default: "webauthn.http_client.default" + * logger?: scalar|null|Param, // A PSR-3 logger to receive logs during the processes // Default: "webauthn.logger.default" + * credential_repository?: scalar|null|Param, // This repository is responsible of the credential storage // Default: "Webauthn\\Bundle\\Repository\\DummyPublicKeyCredentialSourceRepository" + * user_repository?: scalar|null|Param, // This repository is responsible of the user storage // Default: "Webauthn\\Bundle\\Repository\\DummyPublicKeyCredentialUserEntityRepository" + * allowed_origins?: array, + * allow_subdomains?: bool|Param, // Default: false + * secured_rp_ids?: array, + * counter_checker?: scalar|null|Param, // This service will check if the counter is valid. By default it throws an exception (recommended). // Default: "Webauthn\\Counter\\ThrowExceptionIfInvalid" + * top_origin_validator?: scalar|null|Param, // For cross origin (e.g. iframe), this service will be in charge of verifying the top origin. // Default: null + * creation_profiles?: array, + * public_key_credential_parameters?: list, + * attestation_conveyance?: scalar|null|Param, // Default: "none" + * }>, + * request_profiles?: array, + * }>, + * metadata?: bool|array{ // Enable the support of the Metadata Statements. Please read the documentation for this feature. + * enabled?: bool|Param, // Default: false + * mds_repository: scalar|null|Param, // The Metadata Statement repository. + * status_report_repository: scalar|null|Param, // The Status Report repository. + * certificate_chain_checker?: scalar|null|Param, // A Certificate Chain checker. // Default: "Webauthn\\MetadataService\\CertificateChain\\PhpCertificateChainValidator" + * }, + * controllers?: bool|array{ + * enabled?: bool|Param, // Default: false + * creation?: array, + * allow_subdomains?: bool|Param, // Default: false + * secured_rp_ids?: array, + * }>, + * request?: array, + * allow_subdomains?: bool|Param, // Default: false + * secured_rp_ids?: array, + * }>, + * }, + * } + * @psalm-type NbgrpOneloginSamlConfig = array{ // nb:group OneLogin PHP Symfony Bundle configuration + * onelogin_settings?: array/saml/" + * strict?: bool|Param, + * debug?: bool|Param, + * idp: array{ + * entityId: scalar|null|Param, + * singleSignOnService: array{ + * url: scalar|null|Param, + * binding?: scalar|null|Param, + * }, + * singleLogoutService?: array{ + * url?: scalar|null|Param, + * responseUrl?: scalar|null|Param, + * binding?: scalar|null|Param, + * }, + * x509cert?: scalar|null|Param, + * certFingerprint?: scalar|null|Param, + * certFingerprintAlgorithm?: "sha1"|"sha256"|"sha384"|"sha512"|Param, + * x509certMulti?: array{ + * signing?: list, + * encryption?: list, + * }, + * }, + * sp?: array{ + * entityId?: scalar|null|Param, // Default: "/saml/metadata" + * assertionConsumerService?: array{ + * url?: scalar|null|Param, // Default: "/saml/acs" + * binding?: scalar|null|Param, + * }, + * attributeConsumingService?: array{ + * serviceName?: scalar|null|Param, + * serviceDescription?: scalar|null|Param, + * requestedAttributes?: list, + * }>, + * }, + * singleLogoutService?: array{ + * url?: scalar|null|Param, // Default: "/saml/logout" + * binding?: scalar|null|Param, + * }, + * NameIDFormat?: scalar|null|Param, + * x509cert?: scalar|null|Param, + * privateKey?: scalar|null|Param, + * x509certNew?: scalar|null|Param, + * }, + * compress?: array{ + * requests?: bool|Param, + * responses?: bool|Param, + * }, + * security?: array{ + * nameIdEncrypted?: bool|Param, + * authnRequestsSigned?: bool|Param, + * logoutRequestSigned?: bool|Param, + * logoutResponseSigned?: bool|Param, + * signMetadata?: bool|Param, + * wantMessagesSigned?: bool|Param, + * wantAssertionsEncrypted?: bool|Param, + * wantAssertionsSigned?: bool|Param, + * wantNameId?: bool|Param, + * wantNameIdEncrypted?: bool|Param, + * requestedAuthnContext?: mixed, + * requestedAuthnContextComparison?: "exact"|"minimum"|"maximum"|"better"|Param, + * wantXMLValidation?: bool|Param, + * relaxDestinationValidation?: bool|Param, + * destinationStrictlyMatches?: bool|Param, + * allowRepeatAttributeName?: bool|Param, + * rejectUnsolicitedResponsesWithInResponseTo?: bool|Param, + * signatureAlgorithm?: "http://www.w3.org/2000/09/xmldsig#rsa-sha1"|"http://www.w3.org/2000/09/xmldsig#dsa-sha1"|"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"|"http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"|"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"|Param, + * digestAlgorithm?: "http://www.w3.org/2000/09/xmldsig#sha1"|"http://www.w3.org/2001/04/xmlenc#sha256"|"http://www.w3.org/2001/04/xmldsig-more#sha384"|"http://www.w3.org/2001/04/xmlenc#sha512"|Param, + * encryption_algorithm?: "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"|"http://www.w3.org/2001/04/xmlenc#aes128-cbc"|"http://www.w3.org/2001/04/xmlenc#aes192-cbc"|"http://www.w3.org/2001/04/xmlenc#aes256-cbc"|"http://www.w3.org/2009/xmlenc11#aes128-gcm"|"http://www.w3.org/2009/xmlenc11#aes192-gcm"|"http://www.w3.org/2009/xmlenc11#aes256-gcm"|Param, + * lowercaseUrlencoding?: bool|Param, + * }, + * contactPerson?: array{ + * technical?: array{ + * givenName: scalar|null|Param, + * emailAddress: scalar|null|Param, + * }, + * support?: array{ + * givenName: scalar|null|Param, + * emailAddress: scalar|null|Param, + * }, + * administrative?: array{ + * givenName: scalar|null|Param, + * emailAddress: scalar|null|Param, + * }, + * billing?: array{ + * givenName: scalar|null|Param, + * emailAddress: scalar|null|Param, + * }, + * other?: array{ + * givenName: scalar|null|Param, + * emailAddress: scalar|null|Param, + * }, + * }, + * organization?: list, + * }>, + * use_proxy_vars?: bool|Param, // Default: false + * idp_parameter_name?: scalar|null|Param, // Default: "idp" + * entity_manager_name?: scalar|null|Param, + * authn_request?: array{ + * parameters?: list, + * forceAuthn?: bool|Param, // Default: false + * isPassive?: bool|Param, // Default: false + * setNameIdPolicy?: bool|Param, // Default: true + * nameIdValueReq?: scalar|null|Param, // Default: null + * }, + * } + * @psalm-type StimulusConfig = array{ + * controller_paths?: list, + * controllers_json?: scalar|null|Param, // Default: "%kernel.project_dir%/assets/controllers.json" + * } + * @psalm-type UxTranslatorConfig = array{ + * dump_directory?: scalar|null|Param, // Default: "%kernel.project_dir%/var/translations" + * domains?: string|array{ // List of domains to include/exclude from the generated translations. Prefix with a `!` to exclude a domain. + * type?: scalar|null|Param, + * elements?: list, + * }, + * } + * @psalm-type DompdfFontLoaderConfig = array{ + * autodiscovery?: bool|array{ + * paths?: list, + * exclude_patterns?: list, + * file_pattern?: scalar|null|Param, // Default: "/\\.(ttf)$/" + * enabled?: bool|Param, // Default: true + * }, + * auto_install?: bool|Param, // Default: false + * fonts?: list, + * } + * @psalm-type KnpuOauth2ClientConfig = array{ + * http_client?: scalar|null|Param, // Service id of HTTP client to use (must implement GuzzleHttp\ClientInterface) // Default: null + * http_client_options?: array{ + * timeout?: int|Param, + * proxy?: scalar|null|Param, + * verify?: bool|Param, // Use only with proxy option set + * }, + * clients?: array>, + * } + * @psalm-type NelmioCorsConfig = array{ + * defaults?: array{ + * allow_credentials?: bool|Param, // Default: false + * allow_origin?: list, + * allow_headers?: list, + * allow_methods?: list, + * allow_private_network?: bool|Param, // Default: false + * expose_headers?: list, + * max_age?: scalar|null|Param, // Default: 0 + * hosts?: list, + * origin_regex?: bool|Param, // Default: false + * forced_allow_origin_value?: scalar|null|Param, // Default: null + * skip_same_as_origin?: bool|Param, // Default: true + * }, + * paths?: array, + * allow_headers?: list, + * allow_methods?: list, + * allow_private_network?: bool|Param, + * expose_headers?: list, + * max_age?: scalar|null|Param, // Default: 0 + * hosts?: list, + * origin_regex?: bool|Param, + * forced_allow_origin_value?: scalar|null|Param, // Default: null + * skip_same_as_origin?: bool|Param, + * }>, + * } + * @psalm-type JbtronicsSettingsConfig = array{ + * search_paths?: list, + * proxy_dir?: scalar|null|Param, // Default: "%kernel.cache_dir%/jbtronics_settings/proxies" + * proxy_namespace?: scalar|null|Param, // Default: "Jbtronics\\SettingsBundle\\Proxies" + * default_storage_adapter?: scalar|null|Param, // Default: null + * save_after_migration?: bool|Param, // Default: true + * file_storage?: array{ + * storage_directory?: scalar|null|Param, // Default: "%kernel.project_dir%/var/jbtronics_settings/" + * default_filename?: scalar|null|Param, // Default: "settings" + * }, + * orm_storage?: array{ + * default_entity_class?: scalar|null|Param, // Default: null + * prefetch_all?: bool|Param, // Default: true + * }, + * cache?: array{ + * service?: scalar|null|Param, // Default: "cache.app.taggable" + * default_cacheable?: bool|Param, // Default: false + * ttl?: int|Param, // Default: 0 + * invalidate_on_env_change?: bool|Param, // Default: true + * }, + * } + * @psalm-type JbtronicsTranslationEditorConfig = array{ + * translations_path?: scalar|null|Param, // Default: "%translator.default_path%" + * format?: scalar|null|Param, // Default: "xlf" + * xliff_version?: scalar|null|Param, // Default: "2.0" + * use_intl_icu_format?: bool|Param, // Default: false + * writer_options?: list, + * } + * @psalm-type ApiPlatformConfig = array{ + * title?: scalar|null|Param, // The title of the API. // Default: "" + * description?: scalar|null|Param, // The description of the API. // Default: "" + * version?: scalar|null|Param, // The version of the API. // Default: "0.0.0" + * show_webby?: bool|Param, // If true, show Webby on the documentation page // Default: true + * use_symfony_listeners?: bool|Param, // Uses Symfony event listeners instead of the ApiPlatform\Symfony\Controller\MainController. // Default: false + * name_converter?: scalar|null|Param, // Specify a name converter to use. // Default: null + * asset_package?: scalar|null|Param, // Specify an asset package name to use. // Default: null + * path_segment_name_generator?: scalar|null|Param, // Specify a path name generator to use. // Default: "api_platform.metadata.path_segment_name_generator.underscore" + * inflector?: scalar|null|Param, // Specify an inflector to use. // Default: "api_platform.metadata.inflector" + * validator?: array{ + * serialize_payload_fields?: mixed, // Set to null to serialize all payload fields when a validation error is thrown, or set the fields you want to include explicitly. // Default: [] + * query_parameter_validation?: bool|Param, // Deprecated: Will be removed in API Platform 5.0. // Default: true + * }, + * eager_loading?: bool|array{ + * enabled?: bool|Param, // Default: true + * fetch_partial?: bool|Param, // Fetch only partial data according to serialization groups. If enabled, Doctrine ORM entities will not work as expected if any of the other fields are used. // Default: false + * max_joins?: int|Param, // Max number of joined relations before EagerLoading throws a RuntimeException // Default: 30 + * force_eager?: bool|Param, // Force join on every relation. If disabled, it will only join relations having the EAGER fetch mode. // Default: true + * }, + * handle_symfony_errors?: bool|Param, // Allows to handle symfony exceptions. // Default: false + * enable_swagger?: bool|Param, // Enable the Swagger documentation and export. // Default: true + * enable_json_streamer?: bool|Param, // Enable json streamer. // Default: false + * enable_swagger_ui?: bool|Param, // Enable Swagger UI // Default: true + * enable_re_doc?: bool|Param, // Enable ReDoc // Default: true + * enable_entrypoint?: bool|Param, // Enable the entrypoint // Default: true + * enable_docs?: bool|Param, // Enable the docs // Default: true + * enable_profiler?: bool|Param, // Enable the data collector and the WebProfilerBundle integration. // Default: true + * enable_phpdoc_parser?: bool|Param, // Enable resource metadata collector using PHPStan PhpDocParser. // Default: true + * enable_link_security?: bool|Param, // Enable security for Links (sub resources) // Default: false + * collection?: array{ + * exists_parameter_name?: scalar|null|Param, // The name of the query parameter to filter on nullable field values. // Default: "exists" + * order?: scalar|null|Param, // The default order of results. // Default: "ASC" + * order_parameter_name?: scalar|null|Param, // The name of the query parameter to order results. // Default: "order" + * order_nulls_comparison?: "nulls_smallest"|"nulls_largest"|"nulls_always_first"|"nulls_always_last"|null|Param, // The nulls comparison strategy. // Default: null + * pagination?: bool|array{ + * enabled?: bool|Param, // Default: true + * page_parameter_name?: scalar|null|Param, // The default name of the parameter handling the page number. // Default: "page" + * enabled_parameter_name?: scalar|null|Param, // The name of the query parameter to enable or disable pagination. // Default: "pagination" + * items_per_page_parameter_name?: scalar|null|Param, // The name of the query parameter to set the number of items per page. // Default: "itemsPerPage" + * partial_parameter_name?: scalar|null|Param, // The name of the query parameter to enable or disable partial pagination. // Default: "partial" + * }, + * }, + * mapping?: array{ + * imports?: list, + * paths?: list, + * }, + * resource_class_directories?: list, + * serializer?: array{ + * hydra_prefix?: bool|Param, // Use the "hydra:" prefix. // Default: false + * }, + * doctrine?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * doctrine_mongodb_odm?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * oauth?: bool|array{ + * enabled?: bool|Param, // Default: false + * clientId?: scalar|null|Param, // The oauth client id. // Default: "" + * clientSecret?: scalar|null|Param, // The OAuth client secret. Never use this parameter in your production environment. It exposes crucial security information. This feature is intended for dev/test environments only. Enable "oauth.pkce" instead // Default: "" + * pkce?: bool|Param, // Enable the oauth PKCE. // Default: false + * type?: scalar|null|Param, // The oauth type. // Default: "oauth2" + * flow?: scalar|null|Param, // The oauth flow grant type. // Default: "application" + * tokenUrl?: scalar|null|Param, // The oauth token url. // Default: "" + * authorizationUrl?: scalar|null|Param, // The oauth authentication url. // Default: "" + * refreshUrl?: scalar|null|Param, // The oauth refresh url. // Default: "" + * scopes?: list, + * }, + * graphql?: bool|array{ + * enabled?: bool|Param, // Default: false + * default_ide?: scalar|null|Param, // Default: "graphiql" + * graphiql?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * introspection?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * max_query_depth?: int|Param, // Default: 20 + * graphql_playground?: array, + * max_query_complexity?: int|Param, // Default: 500 + * nesting_separator?: scalar|null|Param, // The separator to use to filter nested fields. // Default: "_" + * collection?: array{ + * pagination?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * }, + * }, + * swagger?: array{ + * persist_authorization?: bool|Param, // Persist the SwaggerUI Authorization in the localStorage. // Default: false + * versions?: list, + * api_keys?: array, + * http_auth?: array, + * swagger_ui_extra_configuration?: mixed, // To pass extra configuration to Swagger UI, like docExpansion or filter. // Default: [] + * }, + * http_cache?: array{ + * public?: bool|null|Param, // To make all responses public by default. // Default: null + * invalidation?: bool|array{ // Enable the tags-based cache invalidation system. + * enabled?: bool|Param, // Default: false + * varnish_urls?: list, + * urls?: list, + * scoped_clients?: list, + * max_header_length?: int|Param, // Max header length supported by the cache server. // Default: 7500 + * request_options?: mixed, // To pass options to the client charged with the request. // Default: [] + * purger?: scalar|null|Param, // Specify a purger to use (available values: "api_platform.http_cache.purger.varnish.ban", "api_platform.http_cache.purger.varnish.xkey", "api_platform.http_cache.purger.souin"). // Default: "api_platform.http_cache.purger.varnish" + * xkey?: array{ // Deprecated: The "xkey" configuration is deprecated, use your own purger to customize surrogate keys or the appropriate paramters. + * glue?: scalar|null|Param, // xkey glue between keys // Default: " " + * }, + * }, + * }, + * mercure?: bool|array{ + * enabled?: bool|Param, // Default: false + * hub_url?: scalar|null|Param, // The URL sent in the Link HTTP header. If not set, will default to the URL for MercureBundle's default hub. // Default: null + * include_type?: bool|Param, // Always include @type in updates (including delete ones). // Default: false + * }, + * messenger?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * elasticsearch?: bool|array{ + * enabled?: bool|Param, // Default: false + * hosts?: list, + * }, + * openapi?: array{ + * contact?: array{ + * name?: scalar|null|Param, // The identifying name of the contact person/organization. // Default: null + * url?: scalar|null|Param, // The URL pointing to the contact information. MUST be in the format of a URL. // Default: null + * email?: scalar|null|Param, // The email address of the contact person/organization. MUST be in the format of an email address. // Default: null + * }, + * termsOfService?: scalar|null|Param, // A URL to the Terms of Service for the API. MUST be in the format of a URL. // Default: null + * tags?: list, + * license?: array{ + * name?: scalar|null|Param, // The license name used for the API. // Default: null + * url?: scalar|null|Param, // URL to the license used for the API. MUST be in the format of a URL. // Default: null + * identifier?: scalar|null|Param, // An SPDX license expression for the API. The identifier field is mutually exclusive of the url field. // Default: null + * }, + * swagger_ui_extra_configuration?: mixed, // To pass extra configuration to Swagger UI, like docExpansion or filter. // Default: [] + * overrideResponses?: bool|Param, // Whether API Platform adds automatic responses to the OpenAPI documentation. // Default: true + * error_resource_class?: scalar|null|Param, // The class used to represent errors in the OpenAPI documentation. // Default: null + * validation_error_resource_class?: scalar|null|Param, // The class used to represent validation errors in the OpenAPI documentation. // Default: null + * }, + * maker?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * exception_to_status?: array, + * formats?: array, + * }>, + * patch_formats?: array, + * }>, + * docs_formats?: array, + * }>, + * error_formats?: array, + * }>, + * jsonschema_formats?: list, + * defaults?: array{ + * uri_template?: mixed, + * short_name?: mixed, + * description?: mixed, + * types?: mixed, + * operations?: mixed, + * formats?: mixed, + * input_formats?: mixed, + * output_formats?: mixed, + * uri_variables?: mixed, + * route_prefix?: mixed, + * defaults?: mixed, + * requirements?: mixed, + * options?: mixed, + * stateless?: mixed, + * sunset?: mixed, + * accept_patch?: mixed, + * status?: mixed, + * host?: mixed, + * schemes?: mixed, + * condition?: mixed, + * controller?: mixed, + * class?: mixed, + * url_generation_strategy?: mixed, + * deprecation_reason?: mixed, + * headers?: mixed, + * cache_headers?: mixed, + * normalization_context?: mixed, + * denormalization_context?: mixed, + * collect_denormalization_errors?: mixed, + * hydra_context?: mixed, + * openapi?: mixed, + * validation_context?: mixed, + * filters?: mixed, + * mercure?: mixed, + * messenger?: mixed, + * input?: mixed, + * output?: mixed, + * order?: mixed, + * fetch_partial?: mixed, + * force_eager?: mixed, + * pagination_client_enabled?: mixed, + * pagination_client_items_per_page?: mixed, + * pagination_client_partial?: mixed, + * pagination_via_cursor?: mixed, + * pagination_enabled?: mixed, + * pagination_fetch_join_collection?: mixed, + * pagination_use_output_walkers?: mixed, + * pagination_items_per_page?: mixed, + * pagination_maximum_items_per_page?: mixed, + * pagination_partial?: mixed, + * pagination_type?: mixed, + * security?: mixed, + * security_message?: mixed, + * security_post_denormalize?: mixed, + * security_post_denormalize_message?: mixed, + * security_post_validation?: mixed, + * security_post_validation_message?: mixed, + * composite_identifier?: mixed, + * exception_to_status?: mixed, + * query_parameter_validation_enabled?: mixed, + * links?: mixed, + * graph_ql_operations?: mixed, + * provider?: mixed, + * processor?: mixed, + * state_options?: mixed, + * rules?: mixed, + * policy?: mixed, + * middleware?: mixed, + * parameters?: mixed, + * strict_query_parameter_validation?: mixed, + * hide_hydra_operation?: mixed, + * json_stream?: mixed, + * extra_properties?: mixed, + * map?: mixed, + * route_name?: mixed, + * errors?: mixed, + * read?: mixed, + * deserialize?: mixed, + * validate?: mixed, + * write?: mixed, + * serialize?: mixed, + * priority?: mixed, + * name?: mixed, + * allow_create?: mixed, + * item_uri_template?: mixed, + * ... + * }, + * } + * @psalm-type ConfigType = array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * doctrine?: DoctrineConfig, + * doctrine_migrations?: DoctrineMigrationsConfig, + * security?: SecurityConfig, + * twig?: TwigConfig, + * monolog?: MonologConfig, + * webpack_encore?: WebpackEncoreConfig, + * datatables?: DatatablesConfig, + * liip_imagine?: LiipImagineConfig, + * twig_extra?: TwigExtraConfig, + * gregwar_captcha?: GregwarCaptchaConfig, + * florianv_swap?: FlorianvSwapConfig, + * nelmio_security?: NelmioSecurityConfig, + * turbo?: TurboConfig, + * tfa_webauthn?: TfaWebauthnConfig, + * scheb_two_factor?: SchebTwoFactorConfig, + * webauthn?: WebauthnConfig, + * nbgrp_onelogin_saml?: NbgrpOneloginSamlConfig, + * stimulus?: StimulusConfig, + * ux_translator?: UxTranslatorConfig, + * dompdf_font_loader?: DompdfFontLoaderConfig, + * knpu_oauth2_client?: KnpuOauth2ClientConfig, + * nelmio_cors?: NelmioCorsConfig, + * jbtronics_settings?: JbtronicsSettingsConfig, + * api_platform?: ApiPlatformConfig, + * "when@dev"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * doctrine?: DoctrineConfig, + * doctrine_migrations?: DoctrineMigrationsConfig, + * security?: SecurityConfig, + * twig?: TwigConfig, + * web_profiler?: WebProfilerConfig, + * monolog?: MonologConfig, + * debug?: DebugConfig, + * maker?: MakerConfig, + * webpack_encore?: WebpackEncoreConfig, + * datatables?: DatatablesConfig, + * liip_imagine?: LiipImagineConfig, + * twig_extra?: TwigExtraConfig, + * gregwar_captcha?: GregwarCaptchaConfig, + * florianv_swap?: FlorianvSwapConfig, + * nelmio_security?: NelmioSecurityConfig, + * turbo?: TurboConfig, + * tfa_webauthn?: TfaWebauthnConfig, + * scheb_two_factor?: SchebTwoFactorConfig, + * webauthn?: WebauthnConfig, + * nbgrp_onelogin_saml?: NbgrpOneloginSamlConfig, + * stimulus?: StimulusConfig, + * ux_translator?: UxTranslatorConfig, + * dompdf_font_loader?: DompdfFontLoaderConfig, + * knpu_oauth2_client?: KnpuOauth2ClientConfig, + * nelmio_cors?: NelmioCorsConfig, + * jbtronics_settings?: JbtronicsSettingsConfig, + * jbtronics_translation_editor?: JbtronicsTranslationEditorConfig, + * api_platform?: ApiPlatformConfig, + * }, + * "when@docker"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * doctrine?: DoctrineConfig, + * doctrine_migrations?: DoctrineMigrationsConfig, + * security?: SecurityConfig, + * twig?: TwigConfig, + * monolog?: MonologConfig, + * webpack_encore?: WebpackEncoreConfig, + * datatables?: DatatablesConfig, + * liip_imagine?: LiipImagineConfig, + * twig_extra?: TwigExtraConfig, + * gregwar_captcha?: GregwarCaptchaConfig, + * florianv_swap?: FlorianvSwapConfig, + * nelmio_security?: NelmioSecurityConfig, + * turbo?: TurboConfig, + * tfa_webauthn?: TfaWebauthnConfig, + * scheb_two_factor?: SchebTwoFactorConfig, + * webauthn?: WebauthnConfig, + * nbgrp_onelogin_saml?: NbgrpOneloginSamlConfig, + * stimulus?: StimulusConfig, + * ux_translator?: UxTranslatorConfig, + * dompdf_font_loader?: DompdfFontLoaderConfig, + * knpu_oauth2_client?: KnpuOauth2ClientConfig, + * nelmio_cors?: NelmioCorsConfig, + * jbtronics_settings?: JbtronicsSettingsConfig, + * api_platform?: ApiPlatformConfig, + * }, + * "when@prod"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * doctrine?: DoctrineConfig, + * doctrine_migrations?: DoctrineMigrationsConfig, + * security?: SecurityConfig, + * twig?: TwigConfig, + * monolog?: MonologConfig, + * webpack_encore?: WebpackEncoreConfig, + * datatables?: DatatablesConfig, + * liip_imagine?: LiipImagineConfig, + * twig_extra?: TwigExtraConfig, + * gregwar_captcha?: GregwarCaptchaConfig, + * florianv_swap?: FlorianvSwapConfig, + * nelmio_security?: NelmioSecurityConfig, + * turbo?: TurboConfig, + * tfa_webauthn?: TfaWebauthnConfig, + * scheb_two_factor?: SchebTwoFactorConfig, + * webauthn?: WebauthnConfig, + * nbgrp_onelogin_saml?: NbgrpOneloginSamlConfig, + * stimulus?: StimulusConfig, + * ux_translator?: UxTranslatorConfig, + * dompdf_font_loader?: DompdfFontLoaderConfig, + * knpu_oauth2_client?: KnpuOauth2ClientConfig, + * nelmio_cors?: NelmioCorsConfig, + * jbtronics_settings?: JbtronicsSettingsConfig, + * api_platform?: ApiPlatformConfig, + * }, + * "when@test"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * doctrine?: DoctrineConfig, + * doctrine_migrations?: DoctrineMigrationsConfig, + * security?: SecurityConfig, + * twig?: TwigConfig, + * web_profiler?: WebProfilerConfig, + * monolog?: MonologConfig, + * debug?: DebugConfig, + * webpack_encore?: WebpackEncoreConfig, + * datatables?: DatatablesConfig, + * liip_imagine?: LiipImagineConfig, + * dama_doctrine_test?: DamaDoctrineTestConfig, + * twig_extra?: TwigExtraConfig, + * gregwar_captcha?: GregwarCaptchaConfig, + * florianv_swap?: FlorianvSwapConfig, + * nelmio_security?: NelmioSecurityConfig, + * turbo?: TurboConfig, + * tfa_webauthn?: TfaWebauthnConfig, + * scheb_two_factor?: SchebTwoFactorConfig, + * webauthn?: WebauthnConfig, + * nbgrp_onelogin_saml?: NbgrpOneloginSamlConfig, + * stimulus?: StimulusConfig, + * ux_translator?: UxTranslatorConfig, + * dompdf_font_loader?: DompdfFontLoaderConfig, + * knpu_oauth2_client?: KnpuOauth2ClientConfig, + * nelmio_cors?: NelmioCorsConfig, + * jbtronics_settings?: JbtronicsSettingsConfig, + * api_platform?: ApiPlatformConfig, + * }, + * ..., + * }> + * } + */ +final class App +{ + /** + * @param ConfigType $config + * + * @psalm-return ConfigType + */ + public static function config(array $config): array + { + return AppReference::config($config); + } +} + +namespace Symfony\Component\Routing\Loader\Configurator; + +/** + * This class provides array-shapes for configuring the routes of an application. + * + * Example: + * + * ```php + * // config/routes.php + * namespace Symfony\Component\Routing\Loader\Configurator; + * + * return Routes::config([ + * 'controllers' => [ + * 'resource' => 'routing.controllers', + * ], + * ]); + * ``` + * + * @psalm-type RouteConfig = array{ + * path: string|array, + * controller?: string, + * methods?: string|list, + * requirements?: array, + * defaults?: array, + * options?: array, + * host?: string|array, + * schemes?: string|list, + * condition?: string, + * locale?: string, + * format?: string, + * utf8?: bool, + * stateless?: bool, + * } + * @psalm-type ImportConfig = array{ + * resource: string, + * type?: string, + * exclude?: string|list, + * prefix?: string|array, + * name_prefix?: string, + * trailing_slash_on_root?: bool, + * controller?: string, + * methods?: string|list, + * requirements?: array, + * defaults?: array, + * options?: array, + * host?: string|array, + * schemes?: string|list, + * condition?: string, + * locale?: string, + * format?: string, + * utf8?: bool, + * stateless?: bool, + * } + * @psalm-type AliasConfig = array{ + * alias: string, + * deprecated?: array{package:string, version:string, message?:string}, + * } + * @psalm-type RoutesConfig = array{ + * "when@dev"?: array, + * "when@docker"?: array, + * "when@prod"?: array, + * "when@test"?: array, + * ... + * } + */ +final class Routes +{ + /** + * @param RoutesConfig $config + * + * @psalm-return RoutesConfig + */ + public static function config(array $config): array + { + return $config; + } +} diff --git a/config/routes.yaml b/config/routes.yaml index 2da939a1..4830b774 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -1,3 +1,12 @@ +# yaml-language-server: $schema=../vendor/symfony/routing/Loader/schema/routing.schema.json + +# This file is the entry point to configure the routes of your app. +# Methods with the #[Route] attribute are automatically imported. +# See also https://symfony.com/doc/current/routing.html + +# To list all registered routes, run the following command: +# bin/console debug:router + # Redirect every url without an locale to the locale of the user/the global base locale scan_qr: @@ -15,5 +24,5 @@ redirector: requirements: url: ".*" controller: App\Controller\RedirectController::addLocalePart - # Dont match localized routes (no redirection loop, if no root with that name exists) - condition: "not (request.getPathInfo() matches '/^\\\\/[a-z]{2}(_[A-Z]{2})?\\\\//')" \ No newline at end of file + # Dont match localized routes (no redirection loop, if no root with that name exists) or API prefixed routes + condition: "not (request.getPathInfo() matches '/^\\\\/([a-z]{2}(_[A-Z]{2})?|api)\\\\//')" diff --git a/config/routes/api_platform.yaml b/config/routes/api_platform.yaml new file mode 100644 index 00000000..38f11cba --- /dev/null +++ b/config/routes/api_platform.yaml @@ -0,0 +1,4 @@ +api_platform: + resource: . + type: api_platform + prefix: /api diff --git a/config/routes/dev/php_translation.yaml b/config/routes/dev/php_translation.yaml deleted file mode 100644 index 903b23fb..00000000 --- a/config/routes/dev/php_translation.yaml +++ /dev/null @@ -1,6 +0,0 @@ -_translation_webui: - resource: '@TranslationBundle/Resources/config/routing_webui.yaml' - prefix: /admin - -_translation_profiler: - resource: '@TranslationBundle/Resources/config/routing_symfony_profiler.yaml' diff --git a/config/routes/framework.yaml b/config/routes/framework.yaml index 0fc74bba..bc1feace 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/jbtronics_translation_editor.yaml b/config/routes/jbtronics_translation_editor.yaml new file mode 100644 index 00000000..31409a61 --- /dev/null +++ b/config/routes/jbtronics_translation_editor.yaml @@ -0,0 +1,3 @@ +when@dev: + translation_editor: + resource: '@JbtronicsTranslationEditorBundle/config/routes.php' \ No newline at end of file diff --git a/config/routes/php_translation.yaml b/config/routes/php_translation.yaml deleted file mode 100644 index 96ffd76f..00000000 --- a/config/routes/php_translation.yaml +++ /dev/null @@ -1,3 +0,0 @@ -_translation_edit_in_place: - resource: '@TranslationBundle/Resources/config/routing_edit_in_place.yaml' - prefix: /admin diff --git a/config/routes/security.yaml b/config/routes/security.yaml new file mode 100644 index 00000000..f853be15 --- /dev/null +++ b/config/routes/security.yaml @@ -0,0 +1,3 @@ +_security_logout: + resource: security.route_loader.logout + type: service diff --git a/config/routes/web_profiler.yaml b/config/routes/web_profiler.yaml index 8d85319f..b3b7b4b0 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 24e6a6ac..5021c577 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -1,5 +1,8 @@ +# yaml-language-server: $schema=../vendor/symfony/dependency-injection/Loader/schema/services.schema.json + # This file is the entry point to configure your own services. # Files in the packages/ subdirectory configure your dependencies. +# See also https://symfony.com/doc/current/service_container/import.html # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration @@ -17,8 +20,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: @@ -32,9 +33,8 @@ services: App\: resource: '../src/' exclude: - - '../src/DependencyInjection/' - '../src/Entity/' - - '../src/Kernel.php' + - '../src/Helpers/' # 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,33 +76,10 @@ services: # Only the event classes specified here are saved to DB (set to []) to log all events $whitelist: [] - App\EventSubscriber\LogSystem\EventLoggerSubscriber: - arguments: - $save_changed_fields: '%env(bool:HISTORY_SAVE_CHANGED_FIELDS)%' - $save_changed_data: '%env(bool:HISTORY_SAVE_CHANGED_DATA)%' - $save_removed_data: '%env(bool:HISTORY_SAVE_REMOVED_DATA)%' - $save_new_data: '%env(bool:HISTORY_SAVE_NEW_DATA)%' - tags: - - { name: 'doctrine.event_subscriber' } - - App\EventSubscriber\LogSystem\LogDBMigrationSubscriber: - tags: - - { name: 'doctrine.event_subscriber' } - - App\Form\AttachmentFormType: - arguments: - $allow_attachments_download: '%partdb.attachments.allow_downloads%' - $max_file_size: '%partdb.attachments.max_file_size%' - 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 @@ -140,6 +117,19 @@ services: $saml_role_mapping: '%env(json:SAML_ROLE_MAPPING)%' $update_group_on_login: '%env(bool:SAML_UPDATE_GROUP_ON_LOGIN)%' + + security.access_token_extractor.header.token: + class: Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor + arguments: + $tokenType: 'Token' + + security.access_token_extractor.main: + class: Symfony\Component\Security\Http\AccessToken\ChainAccessTokenExtractor + arguments: + $accessTokenExtractors: + - '@security.access_token_extractor.header' + - '@security.access_token_extractor.header.token' + #################################################################################################################### # Cache #################################################################################################################### @@ -148,29 +138,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 #################################################################################################################### @@ -178,10 +145,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%' @@ -195,10 +158,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%' @@ -211,6 +170,12 @@ services: arguments: $saml_enabled: '%partdb.saml.enabled%' + #################################################################################################################### + # Table settings + #################################################################################################################### + + App\DataTables\Helpers\ColumnSortHelper: + shared: false # Service has a state so not share it between different tables #################################################################################################################### # Label system @@ -229,14 +194,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 #################################################################################################################### @@ -244,38 +201,12 @@ services: arguments: $providers: !tagged_iterator 'app.info_provider' - App\Services\InfoProviderSystem\Providers\Element14Provider: + #################################################################################################################### + # API system + #################################################################################################################### + App\State\PartDBInfoProvider: 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)%' + $default_uri: '%partdb.default_uri%' #################################################################################################################### # Symfony overrides @@ -300,12 +231,17 @@ services: #################################################################################################################### App\Controller\RedirectController: arguments: - $default_locale: '%partdb.locale%' $enforce_index_php: '%env(bool:NO_URL_REWRITE_AVAILABLE)%' - App\Doctrine\Purger\ResetAutoIncrementPurgerFactory: + App\Repository\PartRepository: + arguments: + $translator: '@translator' + tags: ['doctrine.repository_service'] + + App\EventSubscriber\UserSystem\PartUniqueIpnSubscriber: tags: - - { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' } + - { name: doctrine.event_listener, event: onFlush, connection: default } + # We are needing this service inside a migration, where only the container is injected. So we need to define it as public, to access it from the container. App\Services\UserSystem\PermissionPresetsHelper: @@ -315,9 +251,11 @@ services: arguments: $project_dir: '%kernel.project_dir%' - App\Services\System\UpdateAvailableManager: + + App\Doctrine\Middleware\MySQLSSLConnectionMiddlewareWrapper: arguments: - $check_for_updates: '%partdb.check_for_updates%' + $enabled: '%env(bool:DATABASE_MYSQL_USE_SSL_CA)%' + $verify: '%env(bool:DATABASE_MYSQL_SSL_VERIFY_CERT)%' #################################################################################################################### # Monolog @@ -336,7 +274,11 @@ services: tags: - { name: monolog.processor } -when@test: + App\Doctrine\Purger\ResetAutoIncrementPurgerFactory: + tags: + - { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' } + +when@test: &test services: # Decorate the doctrine fixtures load command to use our custom purger by default doctrine.fixtures_load_command.custom: @@ -345,4 +287,4 @@ when@test: arguments: - '@doctrine.fixtures.loader' - '@doctrine' - - { default: '@App\Doctrine\Purger\ResetAutoIncrementPurgerFactory' } \ No newline at end of file + - { default: '@App\Doctrine\Purger\DoNotUsePurgerFactory' } diff --git a/docs/api/authentication.md b/docs/api/authentication.md new file mode 100644 index 00000000..b386c0cd --- /dev/null +++ b/docs/api/authentication.md @@ -0,0 +1,77 @@ +--- +title: Authentication +layout: default +parent: API +nav_order: 2 +--- + +# Authentication + +To use API endpoints, the external application has to authenticate itself, so that Part-DB knows which user is accessing +the data and which permissions +the application should have during the access. Authentication is always bound to a specific user, so the external +applications is acting on behalf of a +specific user. This user limits the permissions of the application so that it can only access data, which the user is +allowed to access. + +The only method currently available for authentication is to use API tokens: + +## API tokens + +An API token is a long alphanumeric string, which is bound to a specific user and can be used to authenticate as this user when accessing the API. +The API token is passed via the `Authorization` HTTP header during the API request, like the +following: `Authorization: Bearer tcp_sdjfks....`. + +{: .important } +> Everybody who knows the API token can access the API as the user, which is bound to the token. So you should treat the +> API token like a password +> and keep it secret. Only share it with trusted applications. + +API tokens can be created and managed on the user settings page in the API token section. You can create as many API +tokens as you want and also delete them again. +When deleting a token, it is immediately invalidated and can not be used anymore, which means that the application can +not access the API anymore with this token. + +### Token permissions and scopes + +API tokens are ultimately limited by the permissions of the user, which belongs to the token. That means that the token +can only access data, that the user is allowed to access, no matter the token permissions. + +But you can further limit the permissions of a token by choosing a specific scope for the token. The scope defines which +subset of permissions the token has, which can be less than the permissions of the user. For example, you can have a +user +with full read and write permissions, but create a token with only read permissions, which can only read data, but not +change anything in the database. + +{: .warning } +> In general, you should always use the least possible permissions for a token, to limit the possible damage, which can +> be done with a stolen token or a bug in the application. +> Only use the full or admin scope, if you really need it, as they could potentially be used to do a lot of damage to +> your Part-DB instance. + +The following token scopes are available: + +* **Read-Only**: The token can only read non-sensitive data (like parts, but no users or groups) from the API and can + not change anything. +* **Edit**: The token can read and write non-sensitive data via the API. This includes creating, updating and deleting + data. This should be enough for most applications. +* **Admin**: The token can read and write all data via the API, including sensitive data like users and groups. This + should only be used for trusted applications, which need to access sensitive data and perform administrative actions. +* **Full**: The token can do anything the user can do, including changing the user's password and creating new tokens. This + should only be used for highly trusted applications!! + +Please note, that in early versions of the API, there might be no endpoints yet, to really perform the actions, which +would be allowed by the token scope. + +### Expiration date + +API tokens can have an expiration date, which means that the token is only valid until the expiration date. After that +the token is automatically invalidated and can not be used anymore. The token is still listed on the user settings page, +and can be deleted there, but the code can not be used to access Part-DB anymore after the expiration date. + +### Get token information + +When authenticating with an API token, you can get information about the currently used token by accessing +the `/api/tokens/current` endpoint. +It gives you information about the token scope, expiration date and the user, which is bound to the token and the last +time the token was used. \ No newline at end of file diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 00000000..441ede9a --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,11 @@ +--- +layout: default +title: API +nav_order: 7 +has_children: true +--- + +# API + +Part-DB provides a REST API to access the data stored in the database. +In this section you can find information about the API and how to use it. diff --git a/docs/api/intro.md b/docs/api/intro.md new file mode 100644 index 00000000..283afbe6 --- /dev/null +++ b/docs/api/intro.md @@ -0,0 +1,229 @@ +--- +title: Introduction +layout: default +parent: API +nav_order: 1 +--- + +# Introduction + +Part-DB provides a [REST API](https://en.wikipedia.org/wiki/REST) to programmatically access the data stored in the +database. +This allows external applications to interact with Part-DB, extend it or integrate it into other applications. + +{: .warning } +> This feature is currently in beta. Please report any bugs you find. +> The API should not be considered stable yet and could change in future versions, without prior notice. +> Some features might be missing or not working yet. +> Also be aware, that there might be security issues in the API, which could allow attackers to access or edit data via +> the API, which +> they normally should not be able to access. So currently you should only use the API with trusted users and trusted +> applications. + +Part-DB uses [API Platform](https://api-platform.com/) to provide the API, which allows for easy creation of REST APIs +with Symfony and gives you a lot of features out of the box. +See the [API Platform documentation](https://api-platform.com/docs/core/) for more details about the API Platform +features and how to use them. + +## Enable the API + +The API is available under the `/api` path, but not reachable without proper permissions. +You have to give the users, which should be able to access the API the proper permissions (Miscellaneous -> API). +Please note that there are two relevant permissions, the first one allows users to access the `/api/` path at all and show the documentation, +and the second one allows them to create API tokens which are needed for the authentication of external applications. + +## Authentication + +To use API endpoints, the external application has to authenticate itself, so that Part-DB knows which user is accessing +the data and +which permissions the application should have. Basically, this is done by creating an API token for a user and then +passing it on every request +with the `Authorization` header as bearer token, so you add a header `Authorization: Bearer `. + +See [Authentication chapter]({% link api/authentication.md %}) for more details. + +## API endpoints + +The API is split into different endpoints, which are reachable under the `/api/` path of your Part-DB instance ( +e.g. `https://your-part-db.local/api/`). +There are various endpoints for each entity type (like `parts`, `manufacturers`, etc.), which allow you to read and write data, and some special endpoints like `search` or `statistics`. + +For example, all API endpoints for managing categories are available under `/api/categories/`. Depending on the exact +path and the HTTP method used, you can read, create, update or delete categories. +For most entities, there are endpoints like this: + +* **GET**: `/api/categories/` - List all categories in the database (with pagination of the results) +* **POST**: `/api/categories/` - Create a new category +* **GET**: `/api/categories/{id}` - Get a specific category by its ID +* **DELETE**: `/api/categories/{id}` - Delete a specific category by its ID +* **PATCH**: `/api/categories/{id}` - Update a specific category by its ID. Only the fields which are sent in the + request are updated, all other fields are left unchanged. + Be aware that you have to set the [JSON Merge Patch](https://datatracker.ietf.org/doc/html/rfc7386) content type + header (`Content-Type: application/merge-patch+json`) for this to work. + +A full (interactive) list of endpoints can be displayed when visiting the `/api/` path in your browser, when you are +logged in with a user, which is allowed to access the API. +There is also a link to this page, on the user settings page in the API token section. +This documentation also lists all available fields for each entity type and the allowed operations. + +## Formats + +The API supports different formats for the request and response data, which you can control via the `Accept` +and `Content-Type` headers. +You should use [JSON-LD](https://json-ld.org/) as format, which is basically JSON with some additional metadata, which +allows you to describe the data in a more structured way and also allows to link between different entities. You can achieve this +by setting `Accept: application/ld+json` header to the API requests. + +To get plain JSON without any metadata or links, use the `Accept: application/json` header. + +Without an `Accept` header (e.g. when you call the endpoint in a browser), the API will return an HTML page with the +documentation, so be sure to include the desired `Accept` header in your API requests. +If you can not control the `Accept` header, you can add a `.json` or `.jsonld` suffix to the URL to enforce a JSON or +JSON-LD response (e.g. `/api/parts.jsonld`). + +## OpenAPI schema + +Part-DB provides a [OpenAPI](https://swagger.io/specification/) (formally Swagger) schema for the API +under `/api/docs.json` (so `https://your-part-db.local/api/docs.json`). +This schema is a machine-readable description of the API, which can be imported into software to test the API or even +automatically generate client libraries for the API. + +API generators which can generate a client library for the API from the schema are available for many programming +languages, like [OpenAPI Generator](https://openapi-generator.tech/). + +An JSONLD/Hydra version of the schema is also available under `/api/docs.jsonld` ( +so `https://your-part-db.local/api/docs.jsonld`). + +## Interactive documentation + +Part-DB provides an interactive documentation for the API, which is available under `/api/docs` ( +so `https://your-part-db.local/api/docs`). +You can pass your API token in the form on the top of the page, to authenticate yourself, and then you can try out the +API directly in the browser. +This is a great way to test the API and see how it works, without having to write any code. + +## Pagination + +By default, all list endpoints are paginated, which means only a certain number of results is returned per request. +To get another page of the results, you have to use the `page` query parameter, which contains the page number you want +to get (e.g. `/api/categories/?page=2`). +When using JSONLD, the links to the next page are also included in the `hydra:view` property of the response. + +To change the size of the pages (the number of items in a single page) use the `itemsPerPage` query parameter ( +e.g. `/api/categories/?itemsPerPage=50`). + +See [API Platform docs](https://api-platform.com/docs/core/pagination) for more infos. + +## Filtering results / Searching + +When retrieving a list of entities, you can restrict the results by various filters. Almost all entities have a search +filter, which allows you to only include entities, which (text) fields match the given search term: For example, if you only want +to get parts, with the Name "BC547", you can use `/api/parts.jsonld?name=BC547`. You can use `%` as a wildcard for multiple +characters in the search term (Be sure to properly encode the search term, if you use special characters). For example, if you want +to get all parts, whose name starts with "BC", you can use `/api/parts.jsonld?name=BC%25` (the `%25` is the url encoded version of `%`). + +There are other filters available for some entities, allowing you to search on other fields, or restricting the results +by numeric values or dates. See the endpoint documentation for the available filters. + +## Filter by associated entities + +To get all parts with a certain category, manufacturer, etc. you can use the `category`, `manufacturer`, etc. query +parameters of the `/api/parts` endpoint. +They are so-called entity filters and accept a comma-separated list of IDs of the entities you want to filter by. +For example, if you want to get all parts with the category "Resistor" (Category ID 1) and "Capacitor" (Category ID 2), +you can use `/api/parts.jsonld?category=1,2`. + +Suffix an id with `+` to suffix, to include all direct children categories of the given category. Use the `++` suffix to +include all children categories recursively. +To get all parts with the category "Resistor" (Category ID 1) and all children categories of "Capacitor" (Category ID +2), you can use `/api/parts.jsonld?category=1,2++`. + +See the endpoint documentation for the available entity filters. + +## Ordering results + +When retrieving a list of entities, you can order the results by various fields using the `order` query parameter. +For example, if you want to get all parts ordered by their name, you can use `/api/parts/?order[name]=asc`. You can use +this parameter multiple times to order by multiple fields. + +See the endpoint documentation for the available fields to order by. + +## Property filter + +Sometimes you only want to get a subset of the properties of an entity, for example when you only need the name of a +part, but not all the other properties. +You can achieve this using the `properties[]` query parameter with the name of the field you want to get. You can use +this parameter multiple times to get multiple fields. +For example, if you only want to get the name and the description of a part, you can +use `/api/parts/123?properties[]=name&properties[]=description`. +It is also possible to use these filters on list endpoints (get collection), to only get a subset of the properties of +all entities in the collection. + +See [API Platform docs](https://api-platform.com/docs/core/filters/#property-filter) for more info. + +## Change comment + +Similar to the changes using Part-DB web interface, you can add a change comment to every change you make via the API, +which will be +visible in the log of the entity. + +You can pass the text for this via the `_comment` query parameter (beware of the proper encoding). For +example `/api/parts/123?_comment=This%20is%20a%20change%20comment`. + +## Creating attachments and parameters + +To create attachments and parameters, use the POST endpoint. Internally there are different types of attachments and +parameters, for each entity type, where the attachments or parameters are used (e.g. PartAttachment for parts, etc.). +The type of the attachment or parameter is automatically determined by the `element` property of the request data if a +IRI is passed. You can use the `_type` property to explicitly set the type of the attachment or parameter (the value must +be the value of the `@type` property of the owning entity. e.g. `Part` for parts). + +For example, to create an attachment on a part, you can use the following request: + +``` +POST /api/attachments + +{ + "name": "front68", + "attachment_type": "/api/attachment_types/1", + "url": "https://invalid.invalid/test.url", + "element": "/api/parts/123" +} +``` + +## Uploading files to attachments + +To upload files to the attachments you can use the special `upload` property of the attachment entity during write operations (POST, PUT, PATCH). +Under `data` you can pass a base64 encoded string of the file content, and under `filename` the name of the file. +Using the `private` property you can control if the file is the attachment should be stored privately or public. + +For example, to upload a file to an attachment, you can use the following request: + +``` +PATCH /api/attachments/123 + +{ + "upload": { + "data": "data:@file/octet-stream;base64,LS0gcGhwTXlB[...]", + "filename": "test.csv", + "private": false + }, + "name": "Rename attachment" +} +``` + +This also works for creating new attachments, by including the `upload` property in the request data along with the other properties. + +Using the `downloadUrl` property of `upload` you can say Part-DB to upload the file specified at the URL set on the attachment. + +``` +PATCH /api/attachments/123 + +{ + "upload": { + "downloadUrl": true + }, + "url": "https://host.invalid/myfile.pdf" +} + +``` \ No newline at end of file diff --git a/docs/assets/getting_started/system_settings.png b/docs/assets/getting_started/system_settings.png new file mode 100644 index 00000000..5a7d7380 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 08701426..14d4500f 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/concepts.md b/docs/concepts.md index 844c50ee..8a3551bd 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -5,50 +5,85 @@ nav_order: 2 --- # Concepts + This page explains the different concepts of Part-DB and what their intended use is: 1. TOC {:toc} -## Part managment +## Part management ### Part -A part is the central concept of Part-DB. A part represents a single kind (or type) of a thing, like an electronic component, an device, an book or similar (depending on what you use Part-DB for). A part entity just represents a certain type of a thing, so if you have 1000 times an BC547 transistor you would create ONE part with the name BC547 and set its quantity to 1000. The individual quantities (so a single BC547 transistor) of a part, should be indistinguishable from each other, so that it does not matter which one of your 1000 things of Part you use. -A part entity have many fields, which can be used to describe it better. Most of the fields are optional: -* **Name** (Required): The name of the part or how you wanna call it. This could be an manufacturer provided name, or a name you thought of your self. The name have to be unique in a single category. -* **Description**: A short (single-line) description of what this part is/does. For longer informations you should use the comment field or the specifications + +A part is the central concept of Part-DB. A part represents a single kind (or type) of a thing, like an electronic +component, a device, a book or similar (depending on what you use Part-DB for). A part entity just represents a certain +type of thing, so if you have 1000 times a BC547 transistor you would create ONE part with the name BC547 and set its +quantity to 1000. The individual quantities (so a single BC547 transistor) of a part, should be indistinguishable from +each other so that it does not matter which one of your 1000 things of Part you use. +A part entity has many fields, which can be used to describe it better. Most of the fields are optional: + +* **Name** (Required): The name of the part or how you want to call it. This could be a manufacturer-provided name, or a + name you thought of yourself. Each name needs to be unique and must exist in a single category only. +* **Description**: A short (single-line) description of what this part is/does. For longer information, you should use + the comment field or the specifications * **Category** (Required): The category (see there) to which this part belongs to. -* **Tags**: The list of tags this part belong to. Tags can be used to group parts logically (similar to the category), but tags are much less strict and formal (they dont have to be defined forehands) and you can assign multiple tags to a part. When clicking on a tag, a list with all parts which have the same tag, is shown. -* **Min Instock**: *Not really implemented yet*. Parts where the total instock is below this value, will show up for ordering. -* **Footprint**: See there. Useful especially for electronic parts, which have one of the common electronic footprints (like DIP8, SMD0805 or similar). If a part has no explicit defined preview picture, the preview picture of its footprint will be shown instead in tables. +* **Tags**: The list of tags this part belongs to. Tags can be used to group parts logically (similar to the category), + but tags are much less strict and formal (they don't have to be defined beforehand) and you can assign multiple tags to + a part. When clicking on a tag, a list with all parts which have the same tag, is shown. +* **Min Instock**: *Not fully implemented yet*. Parts where the total instock is below this value will show up for + ordering. +* **Footprint**: See there. Useful especially for electronic parts, which have one of the common electronic footprints ( + like DIP8, SMD0805 or similar). If a part has no explicitly defined preview picture, the preview picture of its + footprint will be shown instead in tables. * **Manufacturer**: The manufacturer which has manufactured (not sold) this part. See Manufacturer entity for more info. -* **Manufacturer part number** (MPN): If you have used your own name for a part, you can put the part number the manufacturer uses in this field, so that you can find a part also under its manufacturer number. -* **Link to product page**: If you want to link to the manufacturer website of a part, and it is not possible to determine it automatically from the part name, set in the manufacturer entity (or no manfacturer is set), you can set the link here for each part individually. -* **Manufacturing Status**: The manufacturing status of this part, meaning the information about where the part is in its manufacturing lifecycle. -* **Needs review**: If you think parts informations maybe are inaccurate or incomplete and needs some later review/checking, you can set this flag. A part with this flag is marked, so that users know the informations are not completly trustworthy. +* **Manufacturer part number** (MPN): If you have used your own name for a part, you can put the part number the + manufacturer uses in this field so that you can find a part also under its manufacturer number. +* **Link to product page**: If you want to link to the manufacturer website of a part, and it is not possible to + determine it automatically from the part name, set in the manufacturer entity (or no manufacturer is set), you can set + the link here for each part individually. +* **Manufacturing Status**: The manufacturing status of this part, meaning the information about where the part is in + its manufacturing lifecycle. +* **Needs review**: If you think parts information may be inaccurate or incomplete and needs some later + review/checking, you can set this flag. A part with this flag is marked, so that users know the information is not + completely trustworthy. * **Favorite**: Parts with this flag are highlighted in parts lists * **Mass**: The mass of a single piece of this part (so of a single transistor). Given in grams. -* **Internal Part number** (IPN): Each part is automatically assigned an numerical ID which identifies a part in the database. This ID depends on when a part was created and can not be changed. If you want to assign your own unique identifiers, or sync parts identifiers with the identifiers of another database you can use this field. +* **Internal Part Number** (IPN): Each part is automatically assigned a numerical ID that identifies a part in the + database. This ID depends on when a part was created and cannot be changed. If you want to assign your own unique + identifiers, or sync parts identifiers with the identifiers of another database, you can use this field. ### Stock / Part lot -A part can have many stock at multiple different locations. This is represented by part lots / stocks, which consists basically of a storelocation (so where are the parts of this lot are stored) and an amount (how many parts are there). -### Purchase Informations -The purchase informations describe where the part can be bought (at which vendors) and to which prices. -The first part (the order information) describes at which supplier the part can be bought and which is the name of the part under which you can order the part there. -An order information can contain multiple price informations, which describes the prices for the part at the supplier including bulk discount, etc. +A part can have many stocks at multiple different locations. This is represented by part lots/stocks, which consists +basically of a storage location (so where the parts of this lot are stored) and an amount (how many parts are there). + +### Purchase Information + +The purchase information describes where the part can be bought (at which vendors) and at which prices. +The first part (the order information) describes at which supplier the part can be bought and which is the name of the +part under which you can order the part there. +An order information can contain multiple price information, which describes the prices for the part at the supplier +including bulk discount, etc. ### Parameters -Parameters represents various specifications / parameters of a part, like the the maximum current of a diode, etc. The advantage of using parameters instead of just putting the data in the comment field or so, is that you can filter for parameters values (including ranges and more) later on. -Parameters describe can describe numeric values and/or text values for which they can be filtered. This basically allows you to define custom fields on a part. -Using the group field a parameter allows you to group parameters together in the info page later (all parameters with the same group value will be shown under the same group title). +Parameters represent various specifications/parameters of a part, like the maximum current of a diode, etc. The +advantage of using parameters instead of just putting the data in the comment field or so, is that you can filter for +parameter's values (including ranges and more) later on. +Parameters can describe numeric values and/or text values for which they can be filtered. This allows +you to define custom fields on a part. + +Using the group field as a parameter allows you to group parameters together on the info page later (all parameters with +the same group value will be shown under the same group title). ## Core data + ### Category -A category is used to group parts logically by their function (e.g. all NPN transistors would be put in a "NPN-Transistors" category). -Categories are hierarchical structures meaning that you can create logical trees to group categories together. A possible category tree could look like this: +A category is used to group parts logically by their function (e.g. all NPN transistors would be put in a " +NPN-Transistors" category). +Categories are hierarchical structures meaning that you can create logical trees to group categories together. A +possible category tree could look like this: * Active Components * Transistors @@ -60,97 +95,148 @@ Categories are hierarchical structures meaning that you can create logical trees * MCUs * Passive Components * Capacitors - * Resitors + * Resistors ### Supplier -A Supplier is a vendor / distributor where you can buy/order parts. Price informations of parts are associated with a supplier. + +A supplier is a vendor/distributor where you can buy/order parts. Price information of parts is associated with a +supplier. ### Manufacturer -A manufacturer represents the company that manufacturer / build various parts (not necessary sell them). If the manufacturer also sell the parts, you have to create a supplier for that. -### Storelocation -A storelocation represents a place where parts can be stored. This could be a box, a shelf or other things (like the SMD feeder of a machine or so). +A manufacturer represents the company that manufactures/builds various parts (not necessarily sells them). If the +manufacturer also sells the parts, you have to create a supplier for that. -Storelocations are hierarchical to represent storelocations contained in each other. +### Storage location + +A storage location represents a place where parts can be stored. This could be a box, a shelf, or other things (like the +SMD feeder of a machine or so). + +Storage locations are hierarchical to represent storage locations contained in each other. An example tree could look like this: + * Shelf 1 - * Box 1 - * Box 2 - * Box shelf A1 - * Box shelf A2 - * Box shelf B1 - * Box shelf B2 + * Box 1 + * Box 2 + * Box shelf A1 + * Box shelf A2 + * Box shelf B1 + * Box shelf B2 * Shelf 2 * Cupboard -Storelocations should be defined down to the smallest possible location, to make finding the part again easy. +Storage locations should be defined down to the smallest possible location, to make finding the part again easy. ### Footprint -In electronics many components have one of the common components cases / footprints. The footprint entity describes such common footprints, which can be assigned to parts. -You can assign an image (and an 3D model) as an attachment to a footprint, which will be used as preview for parts with this footprint, even if the parts do not have an explicitly assigned preview image. -Footprints are a hierachically which allows you to build logical sorted trees. An example tree could look like this: +In electronics, many components have one of the common components cases/footprints. The footprint entity describes such +common footprints, which can be assigned to parts. +You can assign an image (and a 3D model) as an attachment to a footprint, which will be used as preview for parts with +this footprint, even if the parts do not have an explicitly assigned preview image. + +Footprints are hierarchically which allows you to build logically sorted trees. An example tree could look like this: * Through-Hole components * DIP - * DIP-8 - * DIP-28 - * DIP-28W + * DIP-8 + * DIP-28 + * DIP-28W * TO - * TO-92 + * TO-92 * SMD components * SOIC - * SO-8 + * SO-8 * Resistors - * 0805 - * 0603 + * 0805 + * 0603 ### Measurement Unit -By default part instock is counted in number of individual parts, which is fine for things like electronic components, which exists only in integer quantities. However if you have things with fractional units like the length of a wire or the volume of a liquid, you have to define a measurement unit. -The measurement unit represents a physical quantity like mass, volume or length. -You can define a short unit for it (like m for Meters, or g for gramms) which will be shown, when a quantity of a part with this unit is shown. +By default, part in stock is counted in number of individual parts, which is fine for things like electronic components, +which exist only in integer quantities. However, if you have things with fractional units like the length of a wire or +the volume of a liquid, you have to define a measurement unit. +The measurement unit represents a physical quantity like mass, volume, or length. + +You can define a short unit for it (like m for Meters, or g for grams) which will be shown when a quantity of a part +with this unit is shown. + +In order to cover wider use cases and allow you to define measurement units further, it is possible to define parameters +associated to a measurement unit. These parameters are distinct from a part's parameters and are not inherited. ### Currency -By default all prices are set in the base currency configured for the instance (by default euros). If you want to use multiple currencies together (as e.g. vendors use foreign currencies for their price and you do not want to update the prices for every exchange rate change), you have to define these currencies here. -You can set an exchange rate here in terms of the base currency (or fetch it from the internet if configured). The exchange rate will be used to show users the prices in their preferred currency. +By default, all prices are set in the base currency configured for the instance (by default euros). If you want to use +multiple currencies together (e.g. vendors use foreign currencies for their price, and you do not want to update the +prices for every exchange rate change), you have to define these currencies here. + +You can set an exchange rate here in terms of the base currency (or fetch it from the internet if configured). The +exchange rate will be used to show users the prices in their preferred currency. ## Attachments + ### Attachment -An attachment is an file that can be associated with another entity (like a Part, Storelocation, User, etc.). This could for example be a datasheet in a Part, the logo of a vendor or some CAD drawing of a footprint. -An attachment has an attachment type (see below), which groups the attachments logically (and optionally restricts the allowed file types), a name describing the attachment and a file. The file can either be uploaded to the server and stored there, or given as a link to a file on another webpath. If configured in the settings, it is also possible that the webserver downloads the file from the supplied website and stores it locally on the server. +An attachment is a file that can be associated with another entity (like a Part, location, User, etc.). This could +for example be a datasheet in a Part, the logo of a vendor or some CAD drawing of a footprint. -By default all uploaded files, are accessible for everyone (even non logged in users), if the link is known. If your Part-DB instance is publicly available and you want to store private/sensitve files on it, you should mark the attachment as "Private attachment". Private attachments are only accessible to users, which has the permission to access private attachments. -Please not, that no thumbnails are generated for private attachments, which can have an performance impact. +An attachment has an attachment type (see below), which groups the attachments logically (and optionally restricts the +allowed file types), a name describing the attachment and a file. The file can either be uploaded to the server and +stored there, or given as a link to a file on another web path. If configured in the settings, it is also possible that +the web server downloads the file from the supplied website and stores it locally on the server. -Part-DB ships some preview images for various common footprints like DIP-8 and others, as internal ressources. These can be accessed/searched by typing the keyword in the URL field of a part and choosing one of the choices from the dropdown. +By default, all uploaded files, are accessible for everyone (even non-logged-in users), if the link is known. If your +Part-DB instance is publicly available, and you want to store private/sensitive files on it, you should mark the +attachment as "Private attachment". Private attachments are only accessible to users, which has permission to access +private attachments. +Please note, that no thumbnails are generated for private attachments, which can have a performance impact. -### Preview image / attachment -Most entities with attachments allow you to select one of the defined attachments as "Preview image". You can select an image attachment here, that previews the entity, this could be a picture of a Part, the logo of a manufacturer or supplier, the schematic symbol of a category or the image of an footprint. -The preview image will be shown in various locations together with the entities name. +Part-DB ships some preview images for various common footprints like DIP-8 and others, as internal resources. These can +be accessed/searched by typing the keyword in the URL field of a part and choosing one of the choices from the dropdown. -Please note that as long as the picture is not secret, it should be stored on the Part-DB instance (by upload, or letting Part-DB download the file) and *not* be marked as a private attachments, so that thumbnails can be generated for the picture (which improves performance). +### Preview image/attachment +Most entities with attachments allow you to select one of the defined attachments as "Preview image". You can select an +image attachment here, that previews the entity, this could be a picture of a Part, the logo of a manufacturer or +supplier, the schematic symbol of a category or the image of a footprint. +The preview image will be shown in various locations together with the entity's name. + +Please note that as long as the picture is not secret, it should be stored on the Part-DB instance (by uploading, or +letting Part-DB download the file) and *not* be marked as a private attachment, so that thumbnails can be generated for +the picture (which improves performance). ### Attachment types -Attachment types define logical groups of attachments. For example you could define an attachment group "Datasheets" where all datasheets of Parts, Footprints, etc. belong in, "Pictures" for preview images and more. -You can define file type restrictions, which file types and extensions are allowed for files with that attachment type. + +Attachment types define logical groups of attachments. For example, you could define an attachment group "Datasheets" +where all datasheets of Parts, Footprints, etc. belong in, "Pictures" for preview images and more. +You can define file type restrictions, and which file types and extensions are allowed for files with that attachment type. ## User System -### User -Each person which should be able to use Part-DB (by logging in) is represented by an user entity, which defines things like access rights, the password, and other things. For security reasons, every person which will use Part-DB should use its own personal account with an secret password. This allows to track activity of the users via the log. -There is a special user called `anonymous`, whose access rights are used to determine what an non-logged in user can do. Normally the anonymous user should be the most restricted user. +### User + +Each person who should be able to use Part-DB (by logging in) is represented by a user entity, which defines things +like access rights, the password, and other things. For security reasons, every person who will use Part-DB should use +their own personal account with a secret password. This allows to track activity of the users via the log. + +There is a special user called `anonymous`, whose access rights are used to determine what a non-logged-in user can do. +Normally the anonymous user should be the most restricted user. For simplification of access management users can be assigned to groups. ### Group -A group is entity, to which users can be assigned to. This can be used to logically group users by for example organisational structures and to simplify permissions managment, as you can define groups with access rights for common use cases and then just assign users to them, without the need to change every permission on the users individually. + +A group is an entity, to which users can be assigned to. This can be used to logically group users by for example +organizational structures and to simplify permissions management, as you can define groups with access rights for common +use cases and then just assign users to them, without the need to change every permission on the users individually. ## Labels -### Label profiles -A label profile represents an template for a label (for a storelocation, a part or part lot). It consists of a size, an barcode type and the content. There are various placeholders which can be inserted in the text content and which will be used replaced with data for the actual thing. -You do not have to define a label profile to generate labels (you can just set the settings on the fly in the label dialog), however if you want to generate many labels, it is recommended to save the settings as label profile, to save it for later usage. This ensures that all generated labels look the same. \ No newline at end of file +### Label profiles + +A label profile represents a template for a label (for a storage location, a part or part lot). It consists of a size, a +barcode type and the content. There are various placeholders that can be inserted in the text content and which will be +replaced with data for the actual thing. + +You do not have to define a label profile to generate labels (you can just set the settings on the fly in the label +dialog), however, if you want to generate many labels, it is recommended to save the settings as a label profile, to save +it for later usage. This ensures that all generated labels look the same. diff --git a/docs/configuration.md b/docs/configuration.md index 7dae3923..709c39b3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -6,107 +6,279 @@ nav_order: 5 # Configuration -Part-DBs behavior can be configured to your needs. There are different kind of configuration options: Options which are user changable (changable dynamically via frontend), options which can be configured by environment variables, and options which are only configurable via symfony config files. +Part-DB's behavior can be configured to your needs. There are different kinds of configuration options: Options that are +user-changeable (changeable dynamically via frontend), options that can be configured by environment variables, and +options that are only configurable via Symfony config files. -## User changable -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 users own setting page (where you can also change the password) or via the user admin page: -* **Language**: The language that the users prefers, and which will be used when no language is explicitly specified. Language can still always be changed via the language selector. By default the global configured language is used. -* **Timezone**: The timezone which the user resides in and in which all dates and times should be shown. By default the globally configured language. -* **Theme**: The theme to use for the frontend. Allows the user to choose the frontend design, he prefers. -* **Prefered 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 +## User configuration + +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 +the user admin page: + +* **Language**: The language that the users prefer, and which will be used when no language is explicitly specified. + Language can still always be changed via the language selector. By default, the globally configured language is used. +* **Timezone**: The timezone in which the user resides and in which all dates and times should be shown. By default, the + globally configured language. +* **Theme**: The theme to use for the front end. Allows the user to choose the front end design, he prefers. +* **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 full list of possible env variables. + +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 you to overwrite settings in the web interface. This is useful if you want to enforce certain +settings to be unchangeable by users, or if you want to configure settings in a central place in a deployed environment. +On the settings page, you can hover over a setting to see, which environment variable can be used to overwrite it, it +is shown as tooltip. API keys or similar sensitive data which is overwritten by env variables, are redacted on the web +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. For mysql use a string in the form of `mysql://:@:/` here (e.g. `DATABASE_URL=mysql://user:password@127.0.0.1:3306/part-db`. For sqlite use the following format to specify the absolute path where it should be located `sqlite:///path/part/app.db`. You can use `%kernel.project_dir%` as placeholder for the Part-DB root folder (e.g. `sqlite:///%kernel.project_dir%/var/app.db`) -* `DEFAULT_LANG`: The default language to use serverwide (when no language is explictly specified by a user or via language chooser). Must be something like `en`, `de`, `fr`, etc. -* `DEFAULT_TIMEZONE`: The default timezone to use globally, when a user has not timezone specified. Must be something like `Europe/Berlin`. See [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) under TZ Database name for a list of available options. -* `BASE_CURRENCY`: The currency to use internally for monetary values and when no currency is explictly specified. When migrating from a legacy Part-DB version, this should be the same as the currency in the old Part-DB instance (normally euro). This should be the currency you use the most. **Please note that you can not really change this setting after you have created data**. The value has to be a valid [ISO4217](https://en.wikipedia.org/wiki/ISO_4217) code, like `EUR` or `USD`. -* `INSTANCE_NAME`: The name of your installation. It will be shown as a title in the navbar and other places. By default `Part-DB`, but you can customize it to something likes `ExampleCorp. Inventory`. -* `ALLOW_ATTACHMENT_DOWNLOADS` (allowed values `0` or `1`): By setting this option to 1, users can make Part-DB directly download a file specified as an URL and create it as local file. Please not that this allows users access to all ressources publicly available to the server (so full access to other servers in the same local network), which could be a security risk. -* `USE_GRAVATAR`: Set to `1` to use [gravatar.com](gravatar.com) images for user avatars (as long as they have not set their own picture). The users browsers have to download the pictures from a third-party (gravatars) server, so this might be a privacy risk. -* `MAX_ATTACHMENT_FILE_SIZE`: The maximum file size (in bytes) for attachments. You can use the suffix `K`, `M` or `G` to specify the size in kilobytes, megabytes or gigabytes. By default `100M` (100 megabytes). Please note that this 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. 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 with a slash**. -* `ENFORCE_CHANGE_COMMENTS_FOR`: With this option you can configure, where users are enforced to give a change reason, which will be written to the log. This is a comma separated list of values (e.g. `part_edit,part_delete`). Leave empty to make change comments optional everywhere. Possible values are: - * `part_edit`: Edit operation of a existing part - * `part_delete`: Delete operation of a existing part - * `part_create`: Creation of a new part - * `part_stock_operation`: Stock operation on a part (therefore withdraw, add or move stock) - * `datastructure_edit`: Edit operation of a existing datastructure (e.g. category, manufacturer, ...) - * `datastructure_delete`: Delete operation of a existing datastructure (e.g. category, manufacturer, ...) - * `datastructure_create`: Creation of a new datastructure (e.g. category, manufacturer, ...) -* `CHECK_FOR_UPDATES` (default `1`): Set this to 0, if you do not want Part-DB to connect to GitHub to check for new versions, or if your server can not connect to the internet. -### E-Mail settings -* `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 mail account, you can use the following syntax `MAILER_DSN=smtp://user:password@smtp.mailserver.invalid:587` -* `EMAIL_SENDER_EMAIL`: The email address from which emails should be sent from (in most cases this has to be the same as the email address used for SMTP access) -* `EMAIL_SENDER_NAME`: Similar to `EMAIL_SENDER_EMAIL` but this allows you to specify the name from which the mails are sent from. -* `ALLOW_EMAIL_PW_RESET`: Set this value to true, if you wan to allow users to reset their password via an email notification. You have to configure the mailprovider first before via the MAILER_DSN setting. +* `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 + absolute path where it should be located `sqlite:///path/part/app.db`. You can use `%kernel.project_dir%` as + placeholder for the Part-DB root folder (e.g. `sqlite:///%kernel.project_dir%/var/app.db`) + * For Postgresql use a string in the form of `DATABASE_URL=postgresql://user:password@127.0.0.1:5432/part-db?serverVersion=x.y`. + + Please note that **`serverVersion=x.y`** variable is required due to dependency of Symfony framework. + +* `DATABASE_MYSQL_USE_SSL_CA` (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) (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 + language chooser). Must be something like `en`, `de`, `fr`, etc. +* `DEFAULT_TIMEZONE`: The default timezone to use globally, when a user has no timezone specified. Must be something + like `Europe/Berlin`. See [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) under TZ Database name + for a list of available options. +* `BASE_CURRENCY`: The currency to use internally for monetary values and when no currency is explicitly specified. When + migrating from a legacy Part-DB version, this should be the same as the currency in the old Part-DB instance (normally + euro). This should be the currency you use the most. **Please note that you can not really change this setting after + you have created data**. The value has to be a valid [ISO4217](https://en.wikipedia.org/wiki/ISO_4217) code, + like `EUR` or `USD`. +* `INSTANCE_NAME`: The name of your installation. It will be shown as a title in the navbar and other places. By + default `Part-DB`, but you can customize it to something likes `ExampleCorp. Inventory`. +* `ALLOW_ATTACHMENT_DOWNLOADS` (allowed values `0` or `1`): By setting this option to 1, users can make Part-DB directly + download a file specified as a URL and create it as a local file. Please note that this allows users access to all + resources publicly available to the server (so full access to other servers in the same local network), which could + be a security risk. +* `ATTACHMENT_DOWNLOAD_BY_DEFAULT`: When this is set to 1, the "download external file" checkbox is checked by default + when adding a new attachment. Otherwise, it is unchecked by default. Use this if you wanna download all attachments + locally by default. Attachment download is only possible, when `ALLOW_ATTACHMENT_DOWNLOADS` is set to 1. +* `USE_GRAVATAR`: Set to `1` to use [gravatar.com](https://gravatar.com/) images for user avatars (as long as they have + not set their own picture). The users browsers have to download the pictures from a third-party (gravatar) server, so + this might be a privacy risk. +* `MAX_ATTACHMENT_FILE_SIZE`: The maximum file size (in bytes) for attachments. You can use the suffix `K`, `M` or `G` + 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` (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 + with a slash**. +* `ENFORCE_CHANGE_COMMENTS_FOR`: With this option, you can configure, where users are enforced to give a change reason, + which will be written to the log. This is a comma-separated list of values (e.g. `part_edit,part_delete`). Leave empty + to make change comments optional everywhere. Possible values are: + * `part_edit`: Edit operation of an existing part + * `part_delete`: Delete operation of an existing part + * `part_create`: Creation of a new part + * `part_stock_operation`: Stock operation on a part (therefore withdraw, add or move stock) + * `datastructure_edit`: Edit operation of an existing data structure (e.g. category, manufacturer, ...) + * `datastructure_delete`: Delete operation of an existing data structure (e.g. category, manufacturer, ...) + * `datastructure_create`: Creation of a new data structure (e.g. category, manufacturer, ...) +* `CHECK_FOR_UPDATES` (default `1`): Set this to 0 if you do not want Part-DB to connect to GitHub to check for new + versions, or if your server cannot connect to the internet. +* `APP_SECRET` (env only): This variable is a configuration parameter used for various security-related purposes, + particularly for securing and protecting various aspects of your application. It's a secret key that is used for + cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this + value should be handled as confidential data and not shared publicly. +* `SHOW_PART_IMAGE_OVERLAY`: Set to 0 to disable the part image overlay, which appears if you hover over an image in the + part image gallery +* `IPN_SUGGEST_REGEX`: A global regular expression, that part IPNs have to fulfill. Enforce your own format for your users. +* `IPN_SUGGEST_REGEX_HELP`: Define your own user help text for the Regex format specification. +* `IPN_AUTO_APPEND_SUFFIX`: When enabled, an incremental suffix will be added to the user input when entering an existing +* IPN again upon saving. +* `IPN_SUGGEST_PART_DIGITS`: Defines the fixed number of digits used as the increment at the end of an IPN (Internal Part Number). + IPN prefixes, maintained within part categories and their hierarchy, form the foundation for suggesting complete IPNs. + These suggestions become accessible during IPN input of a part. The constant specifies the digits used to calculate and assign + unique increments for parts within a category hierarchy, ensuring consistency and uniqueness in IPN generation. +* `IPN_USE_DUPLICATE_DESCRIPTION`: When enabled, the part’s description is used to find existing parts with the same + description and to determine the next available IPN by incrementing their numeric suffix for the suggestion list. + +### E-Mail settings (all env only) + +* `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 + mail account, you can use the following syntax `MAILER_DSN=smtp://user:password@smtp.mailserver.invalid:587` +* `EMAIL_SENDER_EMAIL`: The email address from which emails should be sent from (in most cases this has to be the same + as the email address used for SMTP access) +* `EMAIL_SENDER_NAME`: Similar to `EMAIL_SENDER_EMAIL`, but this allows you to specify the name from which the mails are + sent from. +* `ALLOW_EMAIL_PW_RESET`: Set this value to true, if you want to allow users to reset their password via an email + notification. You have to configure the mail provider first before via the MAILER_DSN setting. ### Table related settings -* `TABLE_DEFAULT_PAGE_SIZE`: The default page size for tables. This is the number of rows which are shown per page. Set to `-1` to disable pagination and show all rows at once. -### History/Eventlog related settings +* `TABLE_DEFAULT_PAGE_SIZE`: The default page size for tables. This is the number of rows which are shown per page. Set + to `-1` to disable pagination and show all rows at once. +* `TABLE_PARTS_DEFAULT_COLUMNS`: The columns in parts tables, which are visible by default (when loading table for first + time). + Also specify the default order of the columns. This is a comma separated list of column names. Available columns + are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `partCustomState`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`. + +### History/Eventlog-related settings + The following options are used to configure, which (and how much) data is written to the system log: -* `HISTORY_SAVE_CHANGED_FIELDS`: When this option is set to true, the name of the fields which are changed, are saved to the DB (so for example it is logged that a user has changed, that the user has changed the name and description of the field, but not the data/content of these changes) -* `HISTORY_SAVE_CHANGED_DATA`: When this option is set to true, the changed data is saved to log (so it is logged, that a user has changed the name of a part and what the name was before). This can increase database size, when you have a lot of changes to entities. -* `HISTORY_SAVE_REMOVED_DATA`: When this option is set to true, removed data is saved to log, meaning that you can easily undelete an entity, when it was removed accidentally. -* `HISTORY_SAVE_NEW_DATA`: When this option is set to true, the new data (the data after a change) is saved to element changed log entries. This allows you to easily see the changes between two revisions of an entity. This can increase database size, when you have a lot of changes to entities. -If you wanna 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. +* `HISTORY_SAVE_CHANGED_FIELDS`: When this option is set to true, the name of the fields that are changed, are saved to + the DB (so for example it is logged that a user has changed, that the user has changed the name and description of the + field, but not the data/content of these changes) +* `HISTORY_SAVE_CHANGED_DATA`: When this option is set to true, the changed data is saved to log (so it is logged, that + a user has changed the name of a part and what the name was before). This can increase database size when you have a + lot of changes to entities. +* `HISTORY_SAVE_REMOVED_DATA`: When this option is set to true, removed data is saved to log, meaning that you can + easily undelete an entity, when it was removed accidentally. +* `HISTORY_SAVE_NEW_DATA`: When this option is set to true, the new data (the data after a change) is saved to element + changed log entries. This allows you to easily see the changes between two revisions of an entity. This can increase + database size, when you have a lot of changes to entities. -### Error pages settings -* `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) -* `ERROR_PAGE_SHOW_HELP`: Set this 0, to disable the solution hints shown on an error page. These hints should not contain senstive informations, but could confuse end-users. +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. -### SAML SSO settings -The following settings can be used to enable and configure Single-Sign on via SAML. This allows users to login to Part-DB without entering a username and password, but instead they are redirected to a SAML Identity Provider (IdP) and are logged in automatically. This is especially useful, when you want to use Part-DB in a company, where all users have a SAML account (e.g. via Active Directory or LDAP). -You can find more advanced settings in the `config/packages/hslavich_onelogin_saml.yaml` file. Please note that this file is not backuped by the backup script, so you have to backup it manually, if you want to keep your changes. If you want to edit it on docker, you have to map the file to a volume. +### Error pages settings (all env only) -* `SAML_ENABLED`: When this is set to 1, SAML SSO is enabled and the SSO Login button is shown in the login form. You have to configure the SAML settings below, before you can use this feature. -* `SAML_ROLE_MAPPING`: A [JSON](https://en.wikipedia.org/wiki/JSON) encoded map which specifies how Part-DB should convert the user roles given by SAML attribute `group` should be converted to a Part-DB group (specified by ID). You can use a wildcard `*` to map all otherwise unmapped roles to a certain group. Example: `{"*": 1, "admin": 2, "editor": 3}`. This would map all roles to the group with ID 1, except the role `admin`, which is mapped to the group with ID 2 and the role `editor`, which is mapped to the group with ID 3. -* `SAML_UPDATE_GROUP_ON_LOGIN`: When this is enabled the group of the user is updated on every login of the user based on the SAML role attributes. When this is disabled, the group is only assigned on the first login of the user, and a Part-DB administrator can change the group afterwards by editing the user. -* `SAML_IDP_ENTITY_ID`: The entity ID of your SAML Identity Provider (IdP). You can find this value in the metadata XML file or configuration UI of your IdP. -* `SAML_IDP_SINGLE_SIGN_ON_SERVICE`: The URL of the SAML IdP Single Sign-On Service (SSO). You can find this value in the metadata XML file or configuration UI of your IdP. -* `SAML_IDP_SINGLE_LOGOUT_SERVICE`: The URL of the SAML IdP Single Logout Service (SLO). You can find this value in the metadata XML file or configuration UI of your IdP. -* `SAML_IDP_X509_CERT`: The base64 encoded X.509 public certificate of your SAML IdP. You can find this value in the metadata XML file or configuration UI of your IdP. It should start with `MIIC` and end with `=`. -* `SAML_SP_ENTITY_ID`: The entity ID of your SAML Service Provider (SP). This is the value you have configured for the Part-DB client in your IdP. -* `SAML_SP_X509_CERT`: The public X.509 certificate of your SAML SP (here Part-DB). This is the value you have configured for the Part-DB client in your IdP. It should start with `MIIC` and end with `=`. IdPs like keycloak allows you to generate a public/private key pair for the client which you can setup here and in the `SAML_SP_PRIVATE_KEY` setting. -* `SAML_SP_PRIVATE_KEY`: The private key of your SAML SP (here Part-DB), corresponding the public key specified in `SAML_SP_X509_CERT`. This is the value you have configured for the Part-DB client in your IdP. It should start with `MIIE` and end with `=`. IdPs like keycloak allows you to generate a public/private key pair for the client which you can setup here and in the `SAML_SP_X509_CERT` setting. +* `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) +* `ERROR_PAGE_SHOW_HELP`: Set this 0, to disable the solution hints shown on an error page. These hints should not + contain sensitive information but could confuse end-users. + +### EDA related settings + +* `EDA_KICAD_CATEGORY_DEPTH`: A number, which determines how many levels of Part-DB categories should be shown inside KiCad. + All parts in the selected category and all subcategories are shown in KiCad. + For performance reason this value should not be too high. The default is 0, which means that only the top level categories are shown in KiCad. + 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 (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 +are logged in automatically. This is especially useful when you want to use Part-DB in a company, where all users have +a SAML account (e.g. via Active Directory or LDAP). +You can find more advanced settings in the `config/packages/hslavich_onelogin_saml.yaml` file. Please note that this +file is not backed up by the backup script, so you have to back up it manually, if you want to keep your changes. If you +want to edit it on docker, you have to map the file to a volume. + +* `SAML_ENABLED`: When this is set to 1, SAML SSO is enabled and the SSO Login button is shown in the login form. You + have to configure the SAML settings below before you can use this feature. +* `SAML_BEHIND_PROXY`: Set this to 1, if Part-DB is behind a reverse proxy. See [here]({% link installation/reverse-proxy.md %}) + for more information. Otherwise, leave it to 0 (default.) +* `SAML_ROLE_MAPPING`: A [JSON](https://en.wikipedia.org/wiki/JSON)-encoded map which specifies how Part-DB should + convert the user roles given by SAML attribute `group` should be converted to a Part-DB group (specified by ID). You + can use a wildcard `*` to map all otherwise unmapped roles to a certain group. + Example: `{"*": 1, "admin": 2, "editor": 3}`. This would map all roles to the group with ID 1, except the + role `admin`, which is mapped to the group with ID 2, and the role `editor`, which is mapped to the group with ID 3. +* `SAML_UPDATE_GROUP_ON_LOGIN`: When this is enabled the group of the user is updated on every login of the user based + on the SAML role attributes. When this is disabled, the group is only assigned on the first login of the user, and a + Part-DB administrator can change the group afterward by editing the user. +* `SAML_IDP_ENTITY_ID`: The entity ID of your SAML Identity Provider (IdP). You can find this value in the metadata XML + file or configuration UI of your IdP. +* `SAML_IDP_SINGLE_SIGN_ON_SERVICE`: The URL of the SAML IdP Single Sign-On Service (SSO). You can find this value in + the metadata XML file or configuration UI of your IdP. +* `SAML_IDP_SINGLE_LOGOUT_SERVICE`: The URL of the SAML IdP Single Logout Service (SLO). You can find this value in the + metadata XML file or configuration UI of your IdP. +* `SAML_IDP_X509_CERT`: The base64 encoded X.509 public certificate of your SAML IdP. You can find this value in the + metadata XML file or configuration UI of your IdP. It should start with `MIIC` and end with `=`. +* `SAML_SP_ENTITY_ID`: The entity ID of your SAML Service Provider (SP). This is the value you have configured for the + Part-DB client in your IdP. +* `SAML_SP_X509_CERT`: The public X.509 certificate of your SAML SP (here Part-DB). This is the value you have + configured for the Part-DB client in your IdP. It should start with `MIIC` and end with `=`. IdPs like keycloak allows + you to generate a public/private key pair for the client which you can set up here and in the `SAML_SP_PRIVATE_KEY` + setting. +* `SAML_SP_PRIVATE_KEY`: The private key of your SAML SP (here Part-DB), corresponding the public key specified + in `SAML_SP_X509_CERT`. This is the value you have configured for the Part-DB client in your IdP. It should start + with `MIIE` and end with `=`. IdPs like keycloak allows you to generate a public/private key pair for the client which + you can set up here and in the `SAML_SP_X509_CERT` setting. ### Information provider settings + The settings prefixes with `PROVIDER_*` are used to configure the information providers. See the [information providers]({% link usage/information_provider_system.md %}) page for more information. -### Other / less used options -* `TRUSTED_PROXIES`: Set the IP addresses (or IP blocks) of trusted reverse proxies here. This is needed to get correct IP informations (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 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 instance, 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 support rewrite. In this case, all URL pathes will contain index.php/, which is needed then. Normally this setting do not need to be changed. -* `FIXER_API_KEY`: If you want to automatically retrieve exchange rates for base currencies other than euros, you have 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 mode. (**You should not do this on a publicly accessible server, as it will leak sensitive informations!**) -* `BANNER`: You can configure the text that should be shown as the banner on the homepage. Useful especially for docker container. In all other applications you can just change the `config/banner.md` file. +### Other / less-used options + +* `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` (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` (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`) (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` (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` (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` (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. ## 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 -You can also configure some options via the `config/parameters.yaml` file. This should normally not needed, -and you should know what you are doing, when you change something here. You should expect, that you will have to do some -manual merge, when you have changed something here and update to a newer version of Part-DB. It is possible that configuration -options here will change or completely removed in future versions of Part-DB. -If you change something here, you have to clear the cache, before the changes will take effect with the command `bin/console cache:clear`. +You can also configure some options via the `config/parameters.yaml` file. This should normally not be needed, +and you should know what you are doing when you change something here. You should expect that you will have to do some +manual merges when you have changed something here and update to a newer version of Part-DB. It is possible that +configuration options here will change or be completely removed in future versions of Part-DB. + +If you change something here, you have to clear the cache, before the changes will take effect with the +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 anonymized, by removing the last byte of the IP. This is required by the GDPR (General Data Protection Regulation) in the EU. -* `partdb.sidebar.items`: The panel contents which should be shown in the sidebar by default. You can also change the number of sidebar panels by changing the number of items in this list. +* `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 + anonymized, by removing the last byte of the IP. This is required by the GDPR (General Data Protection Regulation) in + the EU. +* `partdb.sidebar.items`: The panel contents which should be shown in the sidebar by default. You can also change the + number of sidebar panels by changing the number of items in this list. * `partdb.sidebar.root_node_enable`: Show a root node in the sidebar trees, of which all nodes are children of * `partdb.sidebar.root_expanded`: Expand the root node in the sidebar trees by default -* `partdb.available_themes`: The list of available themes a user can choose from. \ No newline at end of file +* `partdb.available_themes`: The list of available themes a user can choose from. diff --git a/docs/index.md b/docs/index.md index 200f2919..c2128946 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,65 +5,83 @@ nav_order: 0 --- # Part-DB + Part-DB is an Open-Source inventory management system for your electronic components. It is installed on a web server and so can be accessed with any browser without the need to install additional software. {: .important-title } > Demo -> -> If you want to test Part-DB without installing it, you can use [this](https://part-db.herokuapp.com) Heroku instance. -> (Or this link for the [German Version](https://part-db.herokuapp.com/de/)). +> +> If you want to test Part-DB without installing it, you can use [this](https://demo.part-db.de/) Heroku instance. +> (Or this link for the [German Version](https://demo.part-db.de/de/)). > > You can log in with username: **user** and password: **user**, to change/create data. > -> Every change to the master branch gets automatically deployed, so it represents the currenct development progress and is -> maybe not completly stable. Please mind, that the free Heroku instance is used, so it can take some time when loading the page +> Every change to the master branch gets automatically deployed, so it represents the current development progress and +> may not be completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading +> the page > for the first time. ## Features -* Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer - and multiple store locations and price information. Parts can be grouped using tags. You can associate various files like datasheets or pictures with the parts. -* Multi-Language support (currently German, English, Russian, Japanese and French (experimental)) -* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the builtin barcode scanner -* User system with groups and detailed (fine granular) permissions. - Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups. Password reset via email can be setuped. -* Optional support for single sign-on (SSO) via SAML (using an intermediate service like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server) + +* Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer, + and multiple store locations and price information. Parts can be grouped using tags. You can associate various files + like datasheets or pictures with the parts. +* Multi-language support (currently German, English, Russian, Japanese, French, Czech, Danish, and Chinese) +* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the built-in barcode scanner +* User system with groups and detailed (fine-grained) permissions. + Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups. + Password reset via email can be set up. +* Optional support for single sign-on (SSO) via SAML (using an intermediate service + like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server) * Import/Export system -* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build this project and directly withdraw all components needed from DB -* Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older versions. -* Responsive design: You can use Part-DB on your PC, your tablet and your smartphone using the same interface. -* MySQL and SQLite supported as database backends +* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build + this project and directly withdraw all components needed from DB +* Event log: Track what changes happen to your inventory, track which user does what. Revert your parts to older + versions. +* Responsive design: You can use Part-DB on your PC, your tablet, and your smartphone using the same interface. +* MySQL, SQLite and PostgreSQL are supported as database backends * Support for rich text descriptions and comments in parts * Support for multiple currencies and automatic update of exchange rates supported * Powerful search and filter function, including parametric search (search for parts according to some specifications) * Easy migration from an existing PartKeepr instance (see [here]({%link partkeepr_migration.md %})) -* Use cloud providers (like Octopart, Digikey, farnell or TME) to automatically get part information, datasheets and prices for parts (see [here]({% link usage/information_provider_system.md %})) +* Use cloud providers (like Octopart, Digikey, Farnell, Mouser, or TME) to automatically get part information, datasheets, and + prices for parts (see [here]({% link usage/information_provider_system.md %})) +* API to access Part-DB from other applications/scripts +* [Integration with KiCad]({%link usage/eda_integration.md %}): Use Part-DB as the central datasource for your + KiCad and see available parts from Part-DB directly inside KiCad. -With these features Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory, -or makerspaces, where many users have should have (controlled) access to the shared inventory. +With these features, Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory, +or makerspaces, where many users should have (controlled) access to the shared inventory. Part-DB is also used by small companies and universities for managing their inventory. ## License + Part-DB is licensed under the GNU Affero General Public License v3.0 (or at your opinion any later). This mostly means that you can use Part-DB for whatever you want (even use it commercially) as long as you publish the source code for every change you make under the AGPL, too. -See [LICENSE](https://github.com/Part-DB/Part-DB-symfony/blob/master/LICENSE) for more informations. +See [LICENSE](https://github.com/Part-DB/Part-DB-symfony/blob/master/LICENSE) for more information. ## Donate for development + If you want to donate to the Part-DB developer, see the sponsor button in the top bar (next to the repo name). -There you will find various methods to support development on a monthly or a one time base. +There you will find various methods to support development on a monthly or a one-time basis. ## Built with -* [Symfony 5](https://symfony.com/): The main framework used for the serverside PHP + +* [Symfony 6](https://symfony.com/): The main framework used for the serverside PHP * [Bootstrap 5](https://getbootstrap.com/) and [Bootswatch](https://bootswatch.com/): Used as website theme * [Fontawesome](https://fontawesome.com/): Used as icon set -* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend Javascript +* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend + Javascript ## Authors -* **Jan Böhmer** - *Inital work and Maintainer* - [Github](https://github.com/jbtronics/) -See also the list of [contributors](https://github.com/Part-DB/Part-DB-symfony/graphs/contributors) who participated in this project. +* **Jan Böhmer** - *Initial work and Maintainer* - [GitHub](https://github.com/jbtronics/) + +See also the list of [contributors](https://github.com/Part-DB/Part-DB-symfony/graphs/contributors) who participated in +this project. Based on the original Part-DB by Christoph Lechner and K. Jacobs diff --git a/docs/installation/choosing_database.md b/docs/installation/choosing_database.md index b3a7a3a8..8a070120 100644 --- a/docs/installation/choosing_database.md +++ b/docs/installation/choosing_database.md @@ -7,24 +7,176 @@ nav_order: 1 # Choosing database: SQLite or MySQL -Part-DB saves its data in a [relational (SQL) database](https://en.wikipedia.org/wiki/Relational_database). Part-DB supports either the use of [SQLite](https://www.sqlite.org/index.html) or [MySQL](https://www.mysql.com/) / [MariaDB](https://mariadb.org/) (which are mostly the same, except for some minor differences). +Part-DB saves its data in a [relational (SQL) database](https://en.wikipedia.org/wiki/Relational_database). + +For this multiple database types are supported, currently these are: + +* [SQLite](https://www.sqlite.org/index.html) +* [MySQL](https://www.mysql.com/) / [MariaDB](https://mariadb.org/) (which are mostly the same, except for some minor + differences) +* [PostgreSQL](https://www.postgresql.org/) + +All these database types allow for the same basic functionality and allow Part-DB to run. However, there are some minor +differences between them, which might be important for you. Therefore the pros and cons of the different database types +are listed here. {: .important } -You have to choose between the database types before you start using Part-DB and **you can not change it (easily) after you have started creating data**. So you should choose the database type for your usecase (and possible future uses). +You have to choose between the database types before you start using Part-DB and **you can not change it (easily) after +you have started creating data**. So you should choose the database type for your use case (and possible future uses). ## Comparison -**SQLite** is the default database type which is configured out of the box. All data is saved in a single file (normally `var/app.db` in the Part-DB folder) and no additional installation or configuration besides Part-DB is needed. -To use **MySQL/MariaDB** as database, you have to install and configure the MySQL server, configure it and create a database and user for Part-DB, which needs some additional work. When using docker you need an additional docker container, and volume for the data +### SQLite -When using **SQLite** The database can be backuped easily by just copying the SQLite file to a safe place. Ideally the **MySQL** database has to be dumped to a SQL file (using `mysqldump`). The `console partdb:backup` command can do this automatically - -However SQLite does not support certain operations like regex search, which has to be emulated by PHP and therefore are pretty slow compared to the same operation at MySQL. In future there might be features that may only be available, when using MySQL. +#### Pros -In general MySQL might perform better for big Part-DB instances with many entries, lots of users and high activity, than SQLite. +* **Easy to use**: No additional installation or configuration is needed, just start Part-DB and it will work out of the box +* **Easy backup**: Just copy the SQLite file to a safe place, and you have a backup, which you can restore by copying it + back. No need to work with SQL dumps -## Conclusion and Suggestion +#### Cons -When you are a hobbyist and use Part-DB for your own small inventory managment with only you as user (or maybe sometimes a few other people), then the easy to use SQLite database will be fine. +* **Performance**: SQLite is not as fast as MySQL or PostgreSQL, especially when using complex queries or many users. +* **Emulated RegEx search**: SQLite does not support RegEx search natively. Part-DB can emulate it, however that is pretty slow. +* **Emulated natural sorting**: SQLite does not support natural sorting natively. Part-DB can emulate it, but it is pretty slow. +* **Limitations with Unicode**: SQLite has limitations in comparisons and sorting of Unicode characters, which might lead to + unexpected behavior when using non-ASCII characters in your data. For example `µ` (micro sign) is not seen as equal to + `μ` (greek minuscule mu), therefore searching for `µ` (micro sign) will not find parts containing `μ` (mu) and vice versa. + The other databases behave more intuitive in this case. +* **No advanced features**: SQLite do no support many of the advanced features of MySQL or PostgreSQL, which might be utilized + in future versions of Part-DB -When you are planning to have a very big database, with a lot of entries and many users which regulary (and concurrently) using Part-DB you should maybe use MySQL as this will scale better. \ No newline at end of file + +### MySQL/MariaDB + +**If possible, it is recommended to use MariaDB 10.7+ (instead of MySQL), as it supports natural sorting of columns natively.** + +#### Pros + +* **Performance**: Compared to SQLite, MySQL/MariaDB will probably perform better, especially in large databases with many + users and high activity. +* **Natural Sorting**: MariaDB 10.7+ supports natural sorting of columns. On other databases it has to be emulated, which is pretty + slow. +* **Native RegEx search**: MySQL supports RegEx search natively, which is faster than emulating it in PHP. +* **Advanced features**: MySQL/MariaDB supports many advanced features, which might be utilized in future versions of Part-DB. +* **Full Unicode support**: MySQL/MariaDB has better support for Unicode characters, which makes it more intuitive to use + non-ASCII characters in your data. + +#### Cons + +* **Additional installation and configuration**: You have to install and configure the MySQL server, create a database and + user for Part-DB, which needs some additional work compared to SQLite. +* **Backup**: The MySQL database has to be dumped to a SQL file (using `mysqldump`). The `console partdb:backup` command can automate this. + + +### PostgreSQL + +#### Pros +* **Performance**: PostgreSQL is known for its performance, especially in large databases with many users and high activity. +* **Advanced features**: PostgreSQL supports many advanced features, which might be utilized in future versions of Part-DB. +* **Full Unicode support**: PostgreSQL has better support for Unicode characters, which makes it more intuitive to use + non-ASCII characters in your data. +* **Native RegEx search**: PostgreSQL supports RegEx search natively, which is faster than emulating it in PHP. +* **Native Natural Sorting**: PostgreSQL supports natural sorting of columns natively in all versions and in general the support for it + is better than on MariaDB. +* **Support of transactional DDL**: PostgreSQL supports transactional DDL, which means that if you encounter a problem during a schema change, +the database will automatically rollback the changes. On MySQL/MariaDB you have to manually rollback the changes, by restoring from a database backup. + +#### Cons +* **New backend**: The support of postgresql is new, and it was not tested as much as the other backends. There might be some bugs caused by this. +* **Additional installation and configuration**: You have to install and configure the PostgreSQL server, create a database and + user for Part-DB, which needs some additional work compared to SQLite. +* **Backup**: The PostgreSQL database has to be dumped to a SQL file (using `pg_dump`). The `console partdb:backup` command can automate this. + + +## Recommendation + +When you are a hobbyist and use Part-DB for your own small inventory management with only you as user (or maybe sometimes +a few other people), then the easy-to-use SQLite database will be fine, as long as you can live with the limitations, stated above. +However using MariaDB (or PostgreSQL), has no disadvantages in that situation (besides the initial setup requirements), so you might +want to use it, to be prepared for future use cases. + +When you are planning to have a very big database, with a lot of entries and many users which regularly using Part-DB, then you should +use MariaDB or PostgreSQL, as they will perform better in that situation and allow for more advanced features. +If you should use MariaDB or PostgreSQL depends on your personal preference and what you already have installed on your servers and +what you are familiar with. + +## Using the different databases + +The only difference in using the different databases, is a different value in the `DATABASE_URL` environment variable in the `.env.local` file +or in the `DATABASE_URL` environment variable in your server or container configuration. It has the shape of a URL, where the scheme (the part before `://`) +is the database type, and the rest is connection information. + +**The env var format below is for the `env.local` file. It might work differently for other env configuration. E.g. in a docker-compose file you have to remove the quotes!** + +### SQLite + +```shell +DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db" +``` + +Here you just need to configure the path to the SQLite file, which is created by Part-DB when performing the database migrations. +The `%kernel.project_dir%` is a placeholder for the path to the project directory, which is replaced by the actual path by Symfony, so that you do not +need to specify the path manually. In the example the database will be created as `app.db` in the `var` directory of your Part-DB installation folder. + +### MySQL/MariaDB + +```shell +DATABASE_URL="mysql://user:password@127.0.0.1:3306/database?serverVersion=8.0.37" +``` + +Here you have to replace `user`, `password` and `database` with the credentials of the MySQL/MariaDB user and the database name you want to use. +The host (here 127.0.0.1) and port should also be specified according to your MySQL/MariaDB server configuration. + +In the `serverVersion` parameter you can specify the version of the MySQL/MariaDB server you are using, in the way the server returns it +(e.g. `8.0.37` for MySQL and `10.4.14-MariaDB`). If you do not know it, you can leave the default value. + +If you want to use a unix socket for the connection instead of a TCP connection, you can specify the socket path in the `unix_socket` parameter. +```shell +DATABASE_URL="mysql://user:password@localhost/database?serverVersion=8.0.37&unix_socket=/var/run/mysqld/mysqld.sock" +``` + +### PostgreSQL + +```shell +DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=12.19&charset=utf8" +``` + +Here you have to replace `db_user`, `db_password` and `db_name` with the credentials of the PostgreSQL user and the database name you want to use. +The host (here 127.0.0.1) and port should also be specified according to your PostgreSQL server configuration. + +In the `serverVersion` parameter you can specify the version of the PostgreSQL server you are using, in the way the server returns it +(e.g. `12.19 (Debian 12.19-1.pgdg120+1)`). If you do not know it, you can leave the default value. + +The `charset` parameter specify the character set of the database. It should be set to `utf8` to ensure that all characters are stored correctly. + +If you want to use a unix socket for the connection instead of a TCP connection, you can specify the socket path in the `host` parameter. +```shell +DATABASE_URL="postgresql://db_user@localhost/db_name?serverVersion=16.6&charset=utf8&host=/var/run/postgresql" +``` + + +## Natural Sorting + +Natural sorting is the sorting of strings in a way that numbers are sorted by their numerical value, not by their ASCII value. + +For example in the classical binary sorting the string `DIP-4`, `DIP-8`, `DIP-16`, `DIP-28` would be sorted as following: + +* `DIP-16` +* `DIP-28` +* `DIP-4` +* `DIP-8` + +In natural sorting, it would be sorted as: + +* `DIP-4` +* `DIP-8` +* `DIP-16` +* `DIP-28` + +Part-DB can sort names in part tables and tree views naturally. PostgreSQL and MariaDB 10.7+ support natural sorting natively, +and it is automatically used if available. + +For SQLite and MySQL < 10.7 it has to be emulated if wanted, which is pretty slow. Therefore it has to be explicitly enabled by setting the +`DATABASE_EMULATE_NATURAL_SORT` environment variable to `1`. If it is 0 the classical binary sorting is used, on these databases. The emulations +might have some quirks and issues, so it is recommended to use a database which supports natural sorting natively, if you want to use it. diff --git a/docs/installation/email.md b/docs/installation/email.md index 08211616..228825a5 100644 --- a/docs/installation/email.md +++ b/docs/installation/email.md @@ -7,31 +7,96 @@ nav_order: 12 # Email -Part-DB can communicate with its users via email. +Part-DB can communicate with its users via email. At the moment this is only used to send password reset links, but in future this will be used for other things too. To make emails work you have to properly configure a mail provider in Part-DB. ## Configuration -Part-DB uses [Symfony Mailer](https://symfony.com/doc/current/mailer.html) to send emails, which supports multiple -automatic mail providers (like MailChimp or SendGrid). If you want to use one of these providers, check the Symfony Mailer documentation for more information. -We will only cover the configuration of a SMTP provider here, which is sufficient for most usecases. -You will need an email account, which you can use send emails from via password-bases SMTP authentication, this account +Part-DB uses [Symfony Mailer](https://symfony.com/doc/current/mailer.html) to send emails, which supports multiple +mail providers (like Mailgun, SendGrid, or Brevo). If you want to use one of these providers, check the Symfony +Mailer documentation for more information. + +We will only cover the configuration of an SMTP provider here, which is sufficient for most use-cases. +You will need an email account, which you can use to send emails from via password-based SMTP authentication, this account should be dedicated to Part-DB. +### Using specialized mail providers (Mailgun, SendGrid, etc.) + +If you want to use a specialized mail provider like Mailgun, SendGrid, Brevo (formerly Sendinblue), Amazon SES, or +Postmark instead of SMTP, you need to install the corresponding Symfony mailer package first. + +#### Docker installation + +If you are using Part-DB in Docker, you can install additional mailer packages by setting the `COMPOSER_EXTRA_PACKAGES` +environment variable in your `docker-compose.yaml`: + +```yaml +environment: + - COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer + - MAILER_DSN=mailgun+api://API_KEY:DOMAIN@default + - EMAIL_SENDER_EMAIL=noreply@yourdomain.com + - EMAIL_SENDER_NAME=Part-DB + - ALLOW_EMAIL_PW_RESET=1 +``` + +You can install multiple packages by separating them with spaces: + +```yaml +environment: + - COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer symfony/sendgrid-mailer +``` + +The packages will be installed automatically when the container starts. + +Common mailer packages: +- `symfony/mailgun-mailer` - For [Mailgun](https://www.mailgun.com/) +- `symfony/sendgrid-mailer` - For [SendGrid](https://sendgrid.com/) +- `symfony/brevo-mailer` - For [Brevo](https://www.brevo.com/) (formerly Sendinblue) +- `symfony/amazon-mailer` - For [Amazon SES](https://aws.amazon.com/ses/) +- `symfony/postmark-mailer` - For [Postmark](https://postmarkapp.com/) + +#### Direct installation (non-Docker) + +If you have installed Part-DB directly on your server (not in Docker), you need to manually install the required +mailer package using composer. + +Navigate to your Part-DB installation directory and run: + +```bash +# Install the package as the web server user +sudo -u www-data composer require symfony/mailgun-mailer + +# Clear the cache +sudo -u www-data php bin/console cache:clear +``` + +Replace `symfony/mailgun-mailer` with the package you need. You can install multiple packages at once: + +```bash +sudo -u www-data composer require symfony/mailgun-mailer symfony/sendgrid-mailer +``` + +After installing the package, configure the `MAILER_DSN` in your `.env.local` file according to the provider's +documentation (see [Symfony Mailer documentation](https://symfony.com/doc/current/mailer.html) for DSN format for +each provider). + +## SMTP Configuration + To configure the SMTP provider, you have to set the following environment variables: -`MAILER_DSN`: You have to provide the SMTP server address and the credentials for the email account here. The format is the following: -`smtp://:@:`. In most cases the username is the email address of the account, and the port is 587. +`MAILER_DSN`: You have to provide the SMTP server address and the credentials for the email account here. The format is +the following: +`smtp://:@:`. In most cases the username is the email address of the +account, and the port is 587. So the resulting DSN could look like this: `smtp://j.doe@mail.invalid:SUPER_SECRET_PA$$WORD@smtp.mail.invalid:587`. `EMAIL_SENDER_EMAIL`: This is the email address which will be used as sender address for all emails sent by Part-DB. -This should be the same email address as the one used in the `MAILER_DSN` (the email adress of your email account): +This should be the same email address as the one used in the `MAILER_DSN` (the email address of your email account): e.g. `j.doe@mail.invalid`. -`EMAIL_SENDER_NAME`: This is the name which will be used as sender name for all emails sent by Part-DB. +`EMAIL_SENDER_NAME`: This is the name which will be used as sender name for all emails sent by Part-DB. This can be anything you want, e.g. `My Part-DB Mailer`. - Now you can enable the possibility to reset password by setting the `ALLOW_EMAIL_PW_RESET` env to `1` (or `true`). \ No newline at end of file diff --git a/docs/installation/index.md b/docs/installation/index.md index e7b59104..aa12e582 100644 --- a/docs/installation/index.md +++ b/docs/installation/index.md @@ -6,4 +6,6 @@ has_children: true --- # Installation -Below you can find some guides to install Part-DB. \ No newline at end of file +Below you can find some guides to install Part-DB. + +For hobbyists without much experience, we recommend the Docker installation or direct installation on Debian. \ No newline at end of file diff --git a/docs/installation/installation_docker.md b/docs/installation/installation_docker.md index ddac7e42..b7048169 100644 --- a/docs/installation/installation_docker.md +++ b/docs/installation/installation_docker.md @@ -7,19 +7,23 @@ nav_order: 2 # Installation of Part-DB via docker -Part-DB can be installed containerized via docker. This is the easiest way to get Part-DB up and running and works on all platforms, -where docker is available (especially recommended for Windows and MacOS). - +Part-DB can be installed containerized via docker. This is the easiest way to get Part-DB up and running and works on +all platforms, +where docker is available (especially recommended for Windows and macOS). {: .warning } -> The methods described here, configure PHP without HTTPS and therefore should only be used locally in a trusted network. +> The methods described here, configure PHP without HTTPS and therefore should only be used locally in a trusted +> network. > If you want to expose Part-DB to the internet, you have to configure a reverse proxy with an SSL certificate! +It is recommended to install Part-DB on a 64-bit system, as the 32-bit version of PHP is affected by the +[Year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) and can not handle dates after 2038 correctly. + ## Docker-compose + Docker-compose configures the needed images and automatically creates the needed containers and volumes. - -1. Install docker and docker-compose like described under https://docs.docker.com/compose/install/ +1. Install docker and docker-compose as described under https://docs.docker.com/compose/install/ 2. Create a folder where the Part-DB data should live 3. Create a file named docker-compose.yaml with the following content: @@ -43,11 +47,18 @@ 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 - # However you can add add any other environment configuration you want here + # However you can add any other environment configuration you want here # See .env file for all available options or https://docs.part-db.de/configuration.html + # !!! Do not use quotes around the values, as they will be interpreted as part of the value and this will lead to errors !!! # The language to use serverwide as default (en, de, ru, etc.) - DEFAULT_LANG=en @@ -64,23 +75,39 @@ services: # 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. + # Override value if you want to show a given text on homepage. # When this is empty the content of config/banner.md is used as banner #- BANNER=This is a test banner
    with a line break + + # If you use a reverse proxy in front of Part-DB, you must configure the trusted proxies IP addresses here (see reverse proxy documentation for more information): + # - TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 + + # If you need to install additional composer packages (e.g., for specific mailer transports), you can specify them here: + # The packages will be installed automatically when the container starts + # - COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer symfony/sendgrid-mailer ``` -4. Customize the settings by changing the environment variables (or add new ones). See [Configuration]({% link configuration.md %}) for more information. + +4. Customize the settings by changing the environment variables (or adding new ones). See [Configuration]({% link + configuration.md %}) for more information. 5. Inside the folder, run + ```bash docker-compose up -d ``` -6. Create the inital database with + +6. Create the initial database with + ```bash docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate ``` -and watch for the password output -6. Part-DB is available under `http://localhost:8080` and you can log in with username `admin` and the password shown before -The docker image uses a SQLite database and all data (database, uploads and other media) is put into folders relative to the docker-compose.yml. +and watch for the password output + +6. Part-DB is available under `http://localhost:8080` and you can log in with the username `admin` and the password shown + before + +The docker image uses a SQLite database and all data (database, uploads, and other media) is put into folders relative to +the docker-compose.yml. ### MySQL @@ -113,34 +140,28 @@ 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 - # However you can add add any other environment configuration you want here + # Uncomment this, if you want to use the automatic database migration feature. With this you do not have to + # run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/ + # folder (under .automigration-backup), so you can restore it, if the migration fails. + # This feature is currently experimental, so use it at your own risk! + # - DB_AUTOMIGRATE=true + + # You can configure Part-DB using the webUI or environment variables + # However you can add any other environment configuration you want here # 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 + # Override value if you want to show a given text on homepage. + # When this is commented out the webUI can be used to configure the banner #- BANNER=This is a test banner
    with a line break + + # If you need to install additional composer packages (e.g., for specific mailer transports), you can specify them here: + # - COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer symfony/sendgrid-mailer database: container_name: partdb_database image: mysql:8.0 - command: --default-authentication-plugin=mysql_native_password + restart: unless-stopped + command: --default-authentication-plugin=mysql_native_password --log-bin-trust-function-creators=1 environment: # Change this Password MYSQL_ROOT_PASSWORD: SECRET_ROOT_PASSWORD @@ -155,9 +176,43 @@ services: ``` +### Installing additional composer packages + +If you need to use specific mailer transports or other functionality that requires additional composer packages, you can +install them automatically at container startup using the `COMPOSER_EXTRA_PACKAGES` environment variable. + +For example, if you want to use Mailgun as your email provider, you need to install the `symfony/mailgun-mailer` package. +Add the following to your docker-compose.yaml environment section: + +```yaml +environment: + - COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer + - MAILER_DSN=mailgun+api://API_KEY:DOMAIN@default +``` + +You can specify multiple packages by separating them with spaces: + +```yaml +environment: + - COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer symfony/sendgrid-mailer +``` + +{: .info } +> The packages will be installed when the container starts. This may increase the container startup time on the first run. +> The installed packages will persist in the container until it is recreated. + +Common mailer packages you might need: +- `symfony/mailgun-mailer` - For Mailgun email service +- `symfony/sendgrid-mailer` - For SendGrid email service +- `symfony/brevo-mailer` - For Brevo (formerly Sendinblue) email service +- `symfony/amazon-mailer` - For Amazon SES email service +- `symfony/postmark-mailer` - For Postmark email service + ### Update Part-DB + You can update Part-DB by pulling the latest image and restarting the container. Then you have to run the database migrations again + ```bash docker-compose pull docker-compose up -d @@ -165,19 +220,30 @@ docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate ``` ## Direct use of docker image -You can use the `jbtronics/part-db1:master` image directly. You have to expose the port 80 to a host port and configure volumes for `/var/www/html/uploads` and `/var/www/html/public/media`. -If you want to use SQLite database (which is default), you have to configure Part-DB to put the database file in a mapped volume via the `DATABASE_URL` environment variable. -For example if you set `DATABASE_URL=sqlite:///%kernel.project_dir%/var/db/app.db` then you will have to map the `/var/www/html/var/db/` folder to the docker container (see docker-compose.yaml for example). +You can use the `jbtronics/part-db1:master` image directly. You have to expose port 80 to a host port and configure +volumes for `/var/www/html/uploads` and `/var/www/html/public/media`. -You also have to create the database like described above in step 4. +If you want to use SQLite database (which is default), you have to configure Part-DB to put the database file in a +mapped volume via the `DATABASE_URL` environment variable. +For example, if you set `DATABASE_URL=sqlite:///%kernel.project_dir%/var/db/app.db` then you will have to map +the `/var/www/html/var/db/` folder to the docker container (see docker-compose.yaml for example). + +You also have to create the database as described above in step 4. ## Running console commands -You can run the console commands described in README by executing `docker exec --user=www-data -it partdb bin/console [command]` + +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 not possible. Login page is just reloading and no error message is shown or something like "CSFR token invalid"*: +*Login is not possible. Login page is just reloading and no error message is shown or something like "CSFR token invalid"*: -Clear all cookies in your browser or use a inkognito tab for Part-DB. -This related to the fact that Part-DB can not set cookies via HTTP, after some webpage has set cookies before under localhost via https. This is a security mechanism of the browser and can not be bypassed by Part-DB. +Clear all cookies in your browser or use an incognito tab for Part-DB. +This is related to the fact that Part-DB can not set cookies via HTTP after some webpages have set cookies before under +localhost via HTTPS. This is a security mechanism of the browser and can not be bypassed by Part-DB. diff --git a/docs/installation/installation_guide-debian.md b/docs/installation/installation_guide-debian.md index 598e04af..b3c61126 100644 --- a/docs/installation/installation_guide-debian.md +++ b/docs/installation/installation_guide-debian.md @@ -1,45 +1,60 @@ --- -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) -This guide shows you how to install Part-DB directly on Debian 11 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 dont need to install this much dependencies. See **TODO** for more information of the docker installation. +# Part-DB installation guide for Debian 12 (Bookworm) + +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 +installation. {: .warning } -> The methods described here, configure PHP without HTTPS and therefore should only be used locally in a trusted network. +> The methods described here, configure PHP without HTTPS and therefore should only be used locally in a trusted +> network. > If you want to expose Part-DB to the internet, you HAVE to configure an SSL connection! +It is recommended to install Part-DB on a 64-bit system, as the 32-bit version of PHP is affected by the +[Year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) and can not handle dates after 2038 correctly. + ## Installation with SQLite database ### Install prerequisites + 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 an PHP interpreter to run. Part-DB needs PHP 8.1 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 it's 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 +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. + +Install PHP with required extensions and apache2: -# Update package list -sudo apt update && sudo apt upgrade -``` -Now you can install PHP 8.1 and required packages (change the 8.1 in the package version according to the version you want to use): ```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 versions shipped in the repositories is pretty old we 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 wget -O /tmp/composer-setup.php https://getcomposer.org/installer @@ -50,15 +65,18 @@ chmod +x /usr/local/bin/composer ``` ### Install yarn and nodejs -To build the frontend (the user interface) Part-DB uses [yarn](https://yarnpkg.com/). As it dependens on nodejs and the shipped versions are pretty old, we install new versions from offical nodejs repository: + +To build the front end (the user interface) Part-DB uses [yarn](https://yarnpkg.com/). As it depends on Node.js and the +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: + ```bash # Add yarn repository curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/yarnkey.gpg >/dev/null @@ -68,46 +86,63 @@ sudo apt update && sudo apt install yarn ``` ### Create a folder for Part-DB and download it -We now have all prerequisites installed and can start to install Part-DB. We will create a folder for Part-DB in a webfolder of apache2 and download it to this folder. The downloading is done via git, which allows you to update easily later. + +We now have all prerequisites installed and can start to install Part-DB. We will create a folder for Part-DB in the +webroot of apache2 and download it to this folder. The downloading is done via git, which allows you to update easily +later. + ```bash # Download Part-DB into the new folder /var/www/partdb git clone https://github.com/Part-DB/Part-DB-symfony.git /var/www/partdb ``` -By default you are now on the latest development version. In most cases you want to use the latest stable version. You can switch to the latest stable version (tagged) by running the following command: +By default, you are now on the latest development version. In most cases, you want to use the latest stable version. You +can switch to the latest stable version (tagged) by running the following command: + ```bash # This finds the latest release/tag and checks it out git checkout $(git describe --tags $(git rev-list --tags --max-count=1)) ``` -Alternatively you can checkout a specific version by running (see [GitHub Relases page](https://github.com/Part-DB/Part-DB-server/releases) for a list of available versions): + +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: + ```bash chown -R www-data:www-data /var/www/partdb ``` For the next steps we should be in the Part-DB folder, so move into it: + ```bash cd /var/www/partdb ``` ### Create configuration for Part-DB -The basic configuration of Part-DB is done by a `.env.local` file in the main directory. Create on by from the default configuration: + +The basic configuration of Part-DB is done by a `.env.local` file in the main directory. Create on by from the default +configuration: + ```bash 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 this can not be changed after creating price informations. +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 + Part-DB depends on several other libraries and components. Install them by running the following commands: + ```bash # Install composer dependencies (please note the sudo command, to run it under the web server user) sudo -u www-data composer install --no-dev -o @@ -121,32 +156,47 @@ sudo yarn build ### Clear cache To ensure everything is working, clear the cache: + ```bash sudo -u www-data php bin/console cache:clear ``` ### Check if everything is installed + To check if everything is installed, run the following command: + ```bash sudo -u www-data php bin/console partdb:check-requirements ``` -The most things should be green, and no red ones. Yellow messages means optional dependencies which are not important but can improve performance and functionality. + +Most things should be green, and no red ones. Yellow messages mean optional dependencies which are not important +but can improve performance and functionality. ### Create a database for Part-DB -Part-DB by default uses a file based sqlite database to store the data. Use the following command to create the database. The database will normally created at `/var/www/partdb/var/app.db`. + +Part-DB by default uses a file-based SQLite database to store the data. Use the following command to create the +database. The database will normally be created at `/var/www/partdb/var/app.db`. + ```bash sudo -u www-data php bin/console doctrine:migrations:migrate ``` + The command will warn you about schema changes and potential data loss. Continue with typing `yes`. -The command will output several lines of informations. Somewhere should be a a yellow background message like `The initial password for the "admin" user is: f502481134`. Write down this password as you will need it later for inital login. +The command will output several lines of information. Somewhere should be a yellow background message +like `The initial password for the "admin" user is: f502481134`. Write down this password as you will need it later for the initial login. ### Configure apache2 to show Part-DB -Part-DB is now configured, but we have to say apache2 to serve Part-DB as web application. This is done by creating a new apache site: + +Part-DB is now configured, but we have to say apache2 to serve Part-DB as web application. This is done by creating a +new apache site: + ```bash sudo nano /etc/apache2/sites-available/partdb.conf ``` + and add the following content (change ServerName and ServerAlias to your needs): + ``` ServerName partdb.lan @@ -163,33 +213,45 @@ and add the following content (change ServerName and ServerAlias to your needs): CustomLog /var/log/apache2/partdb_access.log combined ``` + Activate the new site by: + ```bash sudo ln -s /etc/apache2/sites-available/partdb.conf /etc/apache2/sites-enabled/partdb.conf ``` -Configure apache to show pretty URL pathes for Part-DB (`/label/dialog` instead of `/index.php/label/dialog`): +Configure apache to show pretty URL paths for Part-DB (`/label/dialog` instead of `/index.php/label/dialog`): + ```bash sudo a2enmod rewrite ``` -If you want to access Part-DB via the IP-Address of the server, instead of the domain name, you have to remove the apache2 default configuration with: +If you want to access Part-DB via the IP-Address of the server, instead of the domain name, you have to remove the +apache2 default configuration with: + ```bash sudo rm /etc/apache2/sites-enabled/000-default.conf ``` Restart the apache2 webserver with: + ```bash sudo service apache2 restart ``` -and Part-DB should now be available under `http://YourServerIP` (or `http://partdb.lan` if you configured DNS in your network to point on the server). +and Part-DB should now be available under `http://YourServerIP` (or `http://partdb.lan` if you configured DNS in your +network to point to the server). ### Login to Part-DB -Navigate to the Part-DB web interface and login via the user icon in the top right corner. You can login using the username `admin` and the password you have written down earlier. + +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 + If you want to update your existing Part-DB installation, you just have to run the following commands: + ```bash # Move into Part-DB folder cd /var/www/partdb @@ -218,8 +280,9 @@ 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 database, we will do this later). -Debian 11 does not ship MySQL in its repositories anymore, so we use the compatible MariaDB instead: + +To use a MySQL database, follow the steps from above (except the creation of the database, we will do this later). +Debian 12 does not ship MySQL in its repositories anymore, so we use the compatible MariaDB instead: 1. Install maria-db with: @@ -228,9 +291,11 @@ sudo apt update && sudo apt install mariadb-server ``` 2. Configure maria-db with: + ```bash sudo mysql_secure_installation ``` + When asked for the root password, just press enter, as we have not set a root password yet. In the next steps you are asked if you want to switch to unix_socket authentication, answer with `n` and press enter. Then you are asked if you want to remove anonymous users, answer with `y` and press enter. @@ -239,33 +304,42 @@ Then you are asked if you want to remove the test database and access to it, ans Then you are asked if you want to reload the privilege tables now, answer with `y` and press enter. 3. Create a new database and user for Part-DB: Run the following commands: + ```bash sudo mariadb ``` + A SQL shell will open, in which you can run the following commands to create a new database and user for Part-DB. Replace 'YOUR_SECRET_PASSWORD' with a secure password. + ```sql CREATE DATABASE partdb; GRANT ALL PRIVILEGES ON partdb.* TO 'partdb'@'localhost' IDENTIFIED BY 'YOUR_SECRET_PASSWORD'; ``` -Finally save the changes with: + +Finally, save the changes with: + ```sql FLUSH PRIVILEGES; ``` + and exit the SQL shell with: + ```sql exit ``` 4. Configure Part-DB to use the new database. Open your `.env.local` file and search the line `DATABASE_URL`. -Change it to the following (you have to replace `YOUR_SECRET_PASSWORD` with the password you have choosen in step 3): + Change it to the following (you have to replace `YOUR_SECRET_PASSWORD` with the password you have chosen in step 3): + ``` -DATABASE_URL=DATABASE_URL=mysql://partdb:YOUR_SECRET_PASSWORD@127.0.0.1:3306/partdb +DATABASE_URL=mysql://partdb:YOUR_SECRET_PASSWORD@127.0.0.1:3306/partdb ``` 5. Create the database schema with: + ```bash sudo -u www-data php bin/console doctrine:migrations:migrate ``` -6. The migration step should have shown you a password for the admin user, which you can use now to login to Part-DB. +6. The migration step should have shown you a password for the admin user, which you can use now to log in to Part-DB. diff --git a/docs/installation/kubernetes.md b/docs/installation/kubernetes.md new file mode 100644 index 00000000..86504af2 --- /dev/null +++ b/docs/installation/kubernetes.md @@ -0,0 +1,42 @@ +--- +title: Kubernetes / Helm +layout: default +parent: Installation +nav_order: 5 +--- + +# Kubernetes / Helm Charts + +If you are using Kubernetes, you can use the [helm charts](https://helm.sh/) provided in this [repository](https://github.com/Part-DB/helm-charts). + +## Usage + +[Helm](https://helm.sh) must be installed to use the charts. Please refer to +Helm's [documentation](https://helm.sh/docs) to get started. + +Once Helm has been set up correctly, add the repo as follows: + +`helm repo add part-db https://part-db.github.io/helm-charts` + +If you had already added this repo earlier, run `helm repo update` to retrieve +the latest versions of the packages. You can then run `helm search repo +part-db` to see the charts. + +To install the part-db chart: + + helm install my-part-db part-db/part-db + +To uninstall the chart: + + helm delete my-part-db + +This repository is also available at [ArtifactHUB](https://artifacthub.io/packages/search?repo=part-db). + +## Configuration + +See the README in the [chart directory](https://github.com/Part-DB/helm-charts/tree/main/charts/part-db) for more +information on the available configuration options. + +## Bugreports + +If you find issues related to the helm charts, please open an issue in the [helm-charts repository](https://github.com/Part-DB/helm-charts). \ No newline at end of file diff --git a/docs/installation/nginx.md b/docs/installation/nginx.md index 542c11eb..db209d92 100644 --- a/docs/installation/nginx.md +++ b/docs/installation/nginx.md @@ -6,14 +6,18 @@ nav_order: 10 --- # Nginx -You can also use [nginx](https://www.nginx.com/) as webserver for Part-DB. Setup Part-DB with apache is a bit easier, so + +You can also use [nginx](https://www.nginx.com/) as webserver for Part-DB. Setting up Part-DB with Apache is a bit easier, so this is the method shown in the guides. This guide assumes that you already have a working nginx installation with PHP configured. ## Setup -1. Install composer and yarn like described in the [apache guide]({% link installation/installation_guide-debian.md %}#install-composer). + +1. Install composer and yarn as described in the [apache guide]({% link installation/installation_guide-debian.md + %}#install-composer). 2. Create a folder for Part-DB and install and configure it as described 3. Instead of creating the config for apache, add the following snippet to your nginx config: + ```nginx server { # Redirect all HTTP requests to HTTPS @@ -48,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; @@ -64,4 +73,6 @@ server { ssl_prefer_server_ciphers on; } ``` -4. Restart nginx with `sudo systemctl restart nginx` and you should be able to access Part-DB under your configured domain. \ No newline at end of file + +4. Restart nginx with `sudo systemctl restart nginx` and you should be able to access Part-DB under your configured + domain. \ No newline at end of file diff --git a/docs/installation/proxmox.md b/docs/installation/proxmox.md new file mode 100644 index 00000000..865d2bf4 --- /dev/null +++ b/docs/installation/proxmox.md @@ -0,0 +1,31 @@ +--- +title: Proxmox VE LXC +layout: default +parent: Installation +nav_order: 6 +--- + +# Proxmox VE LXC + +{: .warning } +> The proxmox VE LXC script for Part-DB is developed and maintained by [Proxmox VE Helper-Scripts](https://community-scripts.github.io/ProxmoxVE/) +> and not by the Part-DB developers. Keep in mind that the script is not officially supported by the Part-DB developers. + +If you are using Proxmox VE you can use the scripts provided by [Proxmox VE Helper-Scripts community](https://community-scripts.github.io/ProxmoxVE/scripts?id=part-db) +to easily install Part-DB in a LXC container. + +## Usage + +To create a new LXC container with Part-DB, you can use the following command in the Proxmox VE shell: + +```bash +bash -c "$(wget -qLO - https://github.com/community-scripts/ProxmoxVE/raw/main/ct/part-db.sh)" +``` + +The same command can be used to update an existing Part-DB container. + +See the [helper script website](https://community-scripts.github.io/ProxmoxVE/scripts?id=part-db) for more information. + +## Bugreports + +If you find issues related to the proxmox VE LXC script, please open an issue in the [Proxmox VE Helper-Scripts repository](https://github.com/community-scripts/ProxmoxVE). diff --git a/docs/installation/reverse-proxy.md b/docs/installation/reverse-proxy.md index d42e5a2b..605b93fa 100644 --- a/docs/installation/reverse-proxy.md +++ b/docs/installation/reverse-proxy.md @@ -9,13 +9,32 @@ nav_order: 11 If you want to put Part-DB behind a reverse proxy, you have to configure Part-DB correctly to make it work properly. -You have to set the `TRUSTED_PROXIES` environment variable to the IP address of your reverse proxy -(either in your `docker-compose.yaml` in the case of docker, or `.env.local` in case of direct installation). +You have to set the `TRUSTED_PROXIES` environment variable to the IP address of your reverse proxy +(either in your `docker-compose.yaml` in the case of docker, or `.env.local` in case of direct installation). If you have multiple reverse proxies, you can set multiple IP addresses separated by a comma (or specify a range). -For example, if your reverse proxy has the IP address `192.168.2.10`, your value should be: +For example, if your reverse proxy has the IP address `192.168.2.10`, your value should be: + ``` TRUSTED_PROXIES=192.168.2.10 ``` -Set the `DEFAULT_URI` environment variable to the URL of your Part-DB installation, available from the outside (so via the reverse proxy). \ No newline at end of file +Set the `DEFAULT_URI` environment variable to the URL of your Part-DB installation, available from the outside (so via +the reverse proxy). + +## Part-DB in a subpath via reverse proxy + +If you put Part-DB into a subpath via the reverse proxy, you have to configure your webserver to include `X-Forwarded-Prefix` in the request headers. +For example if you put Part-DB behind a reverse proxy with the URL `https://example.com/partdb`, you have to set the `X-Forwarded-Prefix` header to `/partdb`. + +In apache, you can do this by adding the following line to your virtual host configuration: + +``` +RequestHeader set X-Forwarded-Prefix "/partdb" +``` + +and in nginx, you can do this by adding the following line to your server configuration: + +``` +proxy_set_header X-Forwarded-Prefix "/partdb"; +``` \ No newline at end of file diff --git a/docs/installation/saml_sso.md b/docs/installation/saml_sso.md index 9a89ab1d..f9752546 100644 --- a/docs/installation/saml_sso.md +++ b/docs/installation/saml_sso.md @@ -7,43 +7,56 @@ nav_order: 12 # Single Sign-On via SAML -Part-DB supports Single Sign-On via SAML. This means that you can use your existing SAML identity provider to log in to Part-DB. -Using an intermediate SAML server like [Keycloak](https://www.keycloak.org/), also allows you to connect Part-DB to a LDAP or Active Directory server. +Part-DB supports Single Sign-On via SAML. This means that you can use your existing SAML identity provider to log in to +Part-DB. +Using an intermediate SAML server like [Keycloak](https://www.keycloak.org/), also allows you to connect Part-DB to an +LDAP or Active Directory server. {: .important } -> This feature is for advanced users only. Single Sign-On is useful for large organizations with many users, which are already using SAML for other services. +> This feature is for advanced users only. Single Sign-On is useful for large organizations with many users, which are +> already using SAML for other services. > If you have only one or a few users, you should use the built-in authentication system of Part-DB. -> This guide assumes that you already have an SAML identity provider set up and working, and have a basic understanding of how SAML works. +> This guide assumes that you already have an SAML identity provider set up and working, and have a basic understanding +> of how SAML works. {: .warning } > This feature is currently in beta. Please report any bugs you find. -> So far it has only tested with Keycloak, but it should work with any SAML 2.0 compatible identity provider. +> So far it has only been tested with Keycloak, but it should work with any SAML 2.0 compatible identity provider. -This guide will show you how to configure Part-DB with [Keycloak](https://www.keycloak.org/) as the SAML identity provider, -but it should work with any SAML 2.0 compatible identity provider. +This guide will show you how to configure Part-DB with [Keycloak](https://www.keycloak.org/) as the SAML identity +provider, but it should work with any SAML 2.0 compatible identity provider. -This guide assumes that you have a working Keycloak installation with some users. If you don't, you can follow the [Keycloak Getting Started Guide](https://www.keycloak.org/docs/latest/getting_started/index.html). +This guide assumes that you have a working Keycloak installation with some users. If you don't, you can follow +the [Keycloak Getting Started Guide](https://www.keycloak.org/docs/latest/getting_started/index.html). {: .important } -> Part-DB associates local users with SAML users by their username. That means if the username of a SAML user changes, a new local user will be created (and the old account can not be accessed). -> You should make sure that the username of a SAML user does not change. If you use Keycloak make sure that the possibility to change the username is disabled (which is by default). -> If you really have to rename a SAML user, a Part-DB admin can rename the local user in the Part-DB in the admin panel, to match the new username of the SAML user. +> Part-DB associates local users with SAML users by their username. That means if the username of a SAML user changes, a +> new local user will be created (and the old account can not be accessed). +> You should make sure that the username of a SAML user does not change. If you use Keycloak make sure that the +> possibility to change the username is disabled (which is by default). +> If you really have to rename a SAML user, a Part-DB admin can rename the local user in the Part-DB in the admin panel, +> to match the new username of the SAML user. ## Configure basic SAML connection ### Create a new SAML client -1. First, you need to configure a new SAML client in Keycloak. Login in to your Keycloak admin console and go to the `Clients` page. -2. Click on `Create client` and select `SAML` as type from the dropdown menu. For the client ID, you can use anything you want, but it should be unique. -*It is recommended to set this value to the domain name of your Part-DB installation, with an attached `/sp` (e.g. `https://partdb.yourdomain.invalid/sp`)*. -The name field should be set to something human-readable, like `Part-DB`. -3. Click on `Save` to create the new client. + +1. First, you need to configure a new SAML client in Keycloak. Login in to your Keycloak admin console and go to + the `Clients` page. +2. Click on `Create client` and select `SAML` as type from the dropdown menu. For the client ID, you can use anything + you want, but it should be unique. + *It is recommended to set this value to the domain name of your Part-DB installation, with an attached `/sp` ( + e.g. `https://partdb.yourdomain.invalid/sp`)*. + The name field should be set to something human-readable, like `Part-DB`. +3. Click on `Save` to create a new client. ### Configure the SAML client 1. Now you need to configure the SAML client. Go to the `Settings` tab and set the following values: * Set `Home URL` to the homepage of your Part-DB installation (e.g. `https://partdb.yourdomain.invalid/`). - * Set `Valid redirect URIs` to your homepage with a wildcard at the end (e.g. `https://partdb.yourdomain.invalid/*`). - * Set `Valid post logout redirect URIs` to `+` to allow all urls from the `Valid redirect URIs`. + * Set `Valid redirect URIs` to your homepage with a wildcard at the end ( + e.g. `https://partdb.yourdomain.invalid/*`). + * Set `Valid post logout redirect URIs` to `+` to allow all URLs from the `Valid redirect URIs`. * Set `Name ID format` to `username` * Ensure `Force POST binding` is enabled. * Ensure `Sign documents` is enabled. @@ -52,30 +65,47 @@ The name field should be set to something human-readable, like `Part-DB`. Click on `Save` to save the changes. 2. Go to the `Advanced` tab and set the following values: - * Assertion Consumer Service POST Binding URL to your homepage with `/saml/acs` at the end (e.g. `https://partdb.yourdomain.invalid/saml/acs`). - * Logout Service POST Binding URL to your homepage with `/logout` at the end (e.g. `https://partdb.yourdomain.invalid/logout`). + * Assertion Consumer Service POST Binding URL to your homepage with `/saml/acs` at the end ( + e.g. `https://partdb.yourdomain.invalid/saml/acs`). + * Logout Service POST Binding URL to your homepage with `/logout` at the end ( + e.g. `https://partdb.yourdomain.invalid/logout`). 3. Go to Keys tab and ensure `Client Signature Required` is enabled. -4. In the Keys tab click on `Generate new keys`. This will generate a new key pair for the SAML client. The private key will be downloaded to your computer. +4. In the Keys tab click on `Generate new keys`. This will generate a new key pair for the SAML client. The private key + will be downloaded to your computer. ### Configure Part-DB to use SAML -1. Open the `.env.local` file of Part-DB (or the docker-compose.yaml) for edit -2. Set the `SAMLP_SP_PRIVATE_KEY` environment variable to the content of the private key file you downloaded in the previous step. It should start with `MIEE` and end with `=`. -3. Set the `SAML_SP_X509_CERT` environment variable to the content of the Certificate field shown in the `Keys` tab of the SAML client in Keycloak. It should start with `MIIC` and end with `=`. -4. Set the `SAML_SP_ENTITY_ID` environment variable to the entityID of the SAML client in Keycloak (e.g. `https://partdb.yourdomain.invalid/sp`). -5. In Keycloak navigate to `Realm Settings` -> `SAML 2.0 Identity Provider` (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml/descriptor) to show the SAML metadata. -6. Copy the `entityID` value from the metadata to the `SAML_IDP_ENTITY_ID` configuration variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master`). -7. Copy the `Single Sign-On Service` value from the metadata to the `SAML_IDP_SINGLE_SIGN_ON_SERVICE` configuration variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml`). -8. Copy the `Single Logout Service` value from the metadata to the `SAML_IDP_SINGLE_LOGOUT_SERVICE` configuration variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml/logout`). -9. Copy the `X.509 Certificate` value from the metadata to the `SAML_IDP_X509_CERT` configuration variable of Part-DB (it should start with `MIIC` and should be pretty long). -10. Set the `DEFAULT_URI` to the homepage (on the publicly available domain) of your Part-DB installation (e.g. `https://partdb.yourdomain.invalid/`). It must end with a slash. + +1. Open the `.env.local` file of Part-DB (or the docker-compose.yaml) for editing +2. Set the `SAML_SP_PRIVATE_KEY` environment variable to the content of the private key file you downloaded in the + previous step. It should start with `MIEE` and end with `=`. +3. Set the `SAML_SP_X509_CERT` environment variable to the content of the Certificate field shown in the `Keys` tab of + the SAML client in Keycloak. It should start with `MIIC` and end with `=`. +4. Set the `SAML_SP_ENTITY_ID` environment variable to the entityID of the SAML client in Keycloak ( + e.g. `https://partdb.yourdomain.invalid/sp`). +5. In Keycloak navigate to `Realm Settings` -> `SAML 2.0 Identity Provider` (by default something + like `https://idp.yourdomain.invalid/realms/master/protocol/saml/descriptor) to show the SAML metadata. +6. Copy the `entityID` value from the metadata to the `SAML_IDP_ENTITY_ID` configuration variable of Part-DB (by default + something like `https://idp.yourdomain.invalid/realms/master`). +7. Copy the `Single Sign-On Service` value from the metadata to the `SAML_IDP_SINGLE_SIGN_ON_SERVICE` configuration + variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml`). +8. Copy the `Single Logout Service` value from the metadata to the `SAML_IDP_SINGLE_LOGOUT_SERVICE` configuration + variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml/logout`). +9. Copy the `X.509 Certificate` value from the metadata to the `SAML_IDP_X509_CERT` configuration variable of Part-DB ( + it should start with `MIIC` and should be pretty long). +10. Set the `DEFAULT_URI` to the homepage (on the publicly available domain) of your Part-DB installation ( + e.g. `https://partdb.yourdomain.invalid/`). It must end with a slash. 11. Set the `SAML_ENABLED` configuration in Part-DB to 1 to enable SAML authentication. -When you access the Part-DB login form now, you should see a new button to log in via SSO. Click on it to be redirected to the SAML identity provider and log in. +When you access the Part-DB login form now, you should see a new button to log in via SSO. Click on it to be redirected +to the SAML identity provider and log in. -In the following sections, you will learn how to configure that Part-DB uses the data provided by the SAML identity provider to fill out user informations. +In the following sections, you will learn how to configure that Part-DB uses the data provided by the SAML identity +provider to fill out user information. ### Set user information based on SAML attributes -Part-DB can set basic user information like the username, the real name and the email address based on the SAML attributes provided by the SAML identity provider. + +Part-DB can set basic user information like the username, the real name and the email address based on the SAML +attributes provided by the SAML identity provider. To do this, you need to configure your SAML identity provider to provide the following attributes: * `email` or `urn:oid:1.2.840.113549.1.9.1` for the email address @@ -83,68 +113,125 @@ To do this, you need to configure your SAML identity provider to provide the fol * `lastName` or `urn:oid:2.5.4.4` for the last name * `department` for the department field of the user -You can omit any of these attributes, but then the corresponding field will be empty (but can be overriden by an administrator). -These values are written to Part-DB database, whenever the user logs in via SAML (the user is created on the first login, and updated on every login). +You can omit any of these attributes, but then the corresponding field will be empty (but can be overwritten by an +administrator). +These values are written to Part-DB database, whenever the user logs in via SAML (the user is created on the first +login, and updated on every login). -To configure Keycloak to provide these attributes, you need to go to the `Client scopes` page and select the `sp-dedicatd` client scope (or create a new one). +To configure Keycloak to provide these attributes, you need to go to the `Client scopes` page and select +the `sp-dedicatd` client scope (or create a new one). In the scope configuration page, click on `Add mappers` and `From predefined mappers`. Select the following mappers: + * `X500 email` * `X500 givenName` * `X500 surname` -and click `Add`. Now Part-DB will be provided with the email, first name and last name of the user based on the Keycloak user database. +and click `Add`. Now Part-DB will be provided with the email, first name and last name of the user based on the Keycloak +user database. ### Configure permissions for SAML users -On the first login of a SAML user, Part-DB will create a new user in the database. This user will have the same username as the SAML user, but no password set. The user will be marked as a SAML user, so he can only login via SAML in the future. However in other aspects the user is a normal user, so Part-DB admins can set permissions for SAML users like for any other user and override permissions assigned via groups. -However for large organizations you maybe want to automatically assign permissions to SAML users based on the roles or groups configured in the identity provider. For this purpose Part-DB allows you to map SAML roles or groups to Part-DB groups. See the next section for details. +On the first login of a SAML user, Part-DB will create a new user in the database. This user will have the same username +as the SAML user, but no password set. The user will be marked as a SAML user, so he can only log in via SAML in the +future. However, in other aspects the user is a normal user, so Part-DB admins can set permissions for SAML users like +for any other user and override permissions assigned via groups. + +For large organizations, you maybe want to automatically assign permissions to SAML users based on the roles or +groups configured in the identity provider. For this purpose Part-DB allows you to map SAML roles or groups to Part-DB +groups. See the next section for details. ### Map SAML roles to Part-DB groups -Part-DB allows you to configure a mapping between SAML roles or groups and Part-DB groups. This allows you to automatically assign permissions to SAML users based on the roles or groups configured in the identity provider. For example if a user at your SAML provider has the role `admin`, you can configure Part-DB to assign the `admin` group to this user. This will give the user all permissions of the `admin` group. -For this you need first have to create the groups in Part-DB, to which you want to assign the users and configure their permissions. You will need the IDs of the groups, which you can find in the `System->Group` page of Part-DB in the Info tab. +Part-DB allows you to configure a mapping between SAML roles or groups and Part-DB groups. This allows you to +automatically assign permissions to SAML users based on the roles or groups configured in the identity provider. For +example, if a user at your SAML provider has the role `admin`, you can configure Part-DB to assign the `admin` group to +this user. This will give the user all permissions of the `admin` group. -The map is provided as [JSON](https://en.wikipedia.org/wiki/JSON) encoded map between the SAML role and the group ID, which has the form `{"saml_role": group_id, "*": group_id, ...}`. You can use the `*` key to assign a group to all users, which are not in any other group. The map is configured via the `SAML_ROLE_MAPPING` environment variable, which you can configure via the `.env.local` or `docker-compose.yml` file. Please note that you have to enclose the JSON string in single quotes here, as JSON itself uses double quotes (e.g. `SAML_ROLE_MAPPING='{ "*": 2, "editor": 3, "admin": 1 }`). +For this, you need first have to create the groups in Part-DB, to which you want to assign the users and configure their +permissions. You will need the IDs of the groups, which you can find on the `System->Group` page of Part-DB in the Info +tab. -For example if you want to assign the group with ID 1 (by default admin) to every SAML user which has the role `admin`, the role with ID 3 (by default editor) to every SAML user with the role `editor` and everybody else to the group with ID 2 (by default readonly), you can configure the following map: +The map is provided as [JSON](https://en.wikipedia.org/wiki/JSON) encoded map between the SAML role and the group ID, +which has the form `{"saml_role": group_id, "*": group_id, ...}`. You can use the `*` key to assign a group to all +users, which are not in any other group. The map is configured via the `SAML_ROLE_MAPPING` environment variable, which +you can configure via the `.env.local` or `docker-compose.yml` file. Please note that you have to enclose the JSON +string in single quotes here, as JSON itself uses double quotes ( +e.g. `SAML_ROLE_MAPPING='{ "*": 2, "editor": 3, "admin": 1 }`). + +For example, if you want to assign the group with ID 1 (by default admin) to every SAML user which has the role `admin`, +the role with ID 3 (by default editor) to every SAML user with the role `editor` and everybody else to the group with ID +2 (by default readonly), you can configure the following map: ``` SAML_ROLE_MAPPING='{"admin": 1, "editor": 3, "*": 2}' ``` -Please not that the order of the mapping is important. The first matching role will be assigned to the user. So if you have a user with the roles `admin` and `editor`, he will be assigned to the group with ID 1 (admin) and not to the group with ID 3 (editor), as the `admin` role comes first in the JSON map. -This mean that you should always put the most specific roles (e.g. admins) first of the map and the most general roles (e.g. normal users) later. +Please note that the order of the mapping is important. The first matching role will be assigned to the user. So if you +have a user with the roles `admin` and `editor`, he will be assigned to the group with ID 1 (admin) and not to the group +with ID 3 (editor), as the `admin` role comes first in the JSON map. +This mean that you should always put the most specific roles (e.g. admins) first of the map and the most general roles ( +e.g. normal users) later. -If you want to assign users with a certain role to a empty group, provide the group ID -1 as the value. This is not a valid group ID, so the user will not be assigned to any group. +If you want to assign users with a certain role to an empty group, provide the group ID -1 as the value. This is not a +valid group ID, so the user will not be assigned to any group. -The SAML roles (or groups depending on your configuration), have to be supplied via a SAML attribute `group`. You have to configure your SAML identity provider to provide this attribute. For example in Keycloak you can configure this attribute in the `Client scopes` page. Select the `sp-dedicated` client scope (or create a new one) and click on `Add mappers`. Select `Role mapping` or `Group membership`, change the field name and click `Add`. Now Part-DB will be provided with the groups of the user based on the Keycloak user database. +The SAML roles (or groups depending on your configuration), have to be supplied via a SAML attribute `group`. You have +to configure your SAML identity provider to provide this attribute. For example, in Keycloak you can configure this +attribute on the `Client scopes` page. Select the `sp-dedicated` client scope (or create a new one) and click +on `Add mappers`. Select `Role mapping` or `Group membership`, change the field name, and click `Add`. Now Part-DB will +be provided with the groups of the user based on the Keycloak user database. -By default, the group is assigned to the user on the first login and updated on every login based on the SAML attributes. This allows you to configure the groups in the SAML identity provider and the users will automatically stay up to date with their permissions. However, if you want to disable this behavior (and let the Part-DB admins configure the groups manually, after the first login), you can set the `SAML_UPDATE_GROUP_ON_LOGIN` environment variable to `false`. If you want to disable the automatic group assignment completly (so not even on the first login of a user), set the `SAML_ROLE_MAPPING` to `{}` (empty JSON object). +By default, the group is assigned to the user on the first login and updated on every login based on the SAML +attributes. This allows you to configure the groups in the SAML identity provider and the users will automatically stay +up to date with their permissions. However, if you want to disable this behavior (and let the Part-DB admins configure +the groups manually, after the first login), you can set the `SAML_UPDATE_GROUP_ON_LOGIN` environment variable +to `false`. If you want to disable the automatic group assignment completely (so not even on the first login of a user), +set the `SAML_ROLE_MAPPING` to `{}` (empty JSON object). ## Overview of possible SAML attributes used by Part-DB -The following table shows all SAML attributes, which can be usedby Part-DB. If your identity provider is configured to provide these attributes, you can use to automatically fill the corresponding fields of the user in Part-DB. + +The following table shows all SAML attributes, which can be used by Part-DB. If your identity provider is configured to +provide these attributes, you can use to automatically fill the corresponding fields of the user in Part-DB. | SAML attribute | Part-DB user field | Description | -|-------------------------------------------|-------------------|-------------------------------------------------------------------| -| `urn:oid:1.2.840.113549.1.9.1` or `email` | email | The email address of the user. | -| `urn:oid:2.5.4.42` or `firstName` | firstName | The first name of the user. | -| `urn:oid:2.5.4.4` or `lastName` | lastName | The last name of the user. | -| `department` | department | The department of the user. | -| `group` | group | The group of the user (determined by `SAML_ROLE_MAPPING` option). | +|-------------------------------------------|--------------------|-------------------------------------------------------------------| +| `urn:oid:1.2.840.113549.1.9.1` or `email` | email | The email address of the user. | +| `urn:oid:2.5.4.42` or `firstName` | firstName | The first name of the user. | +| `urn:oid:2.5.4.4` or `lastName` | lastName | The last name of the user. | +| `department` | department | The department of the user. | +| `group` | group | The group of the user (determined by `SAML_ROLE_MAPPING` option). | ## Use SAML Login for existing users -Part-DB distinguishes between local users and SAML users. Local users are users, which can login via Part-DB login form and which use the password (hash) saved in the Part-DB database. SAML users are stored in the database too (they are created on the first login of the user via SAML), but they use the SAML identity provider to authenticate the user and have no password stored in the database. When you try you will get an error message. -For security reasons it is not possible to authenticate via SAML as a local user (and vice versa). So if you have existing users in your Part-DB database and want them to be able to login via SAML in the future, you can use the `php bin/console partdb:user:convert-to-saml-user username` command to convert them to SAML users. This will remove the password hash from the database and mark them as SAML users, so they can login via SAML in the future. +Part-DB distinguishes between local users and SAML users. Local users are users, that can log in via the Part-DB login form +and use the password (hash) saved in the Part-DB database. SAML users are stored in the database too (they are +created on the first login of the user via SAML), but they use the SAML identity provider to authenticate the user and +have no password stored in the database. When you try you will get an error message. -The reverse is also possible: If you have existing SAML users and want them to be able to login via the Part-DB login form, you can use the `php bin/console partdb:user:convert-to-saml-user --to-local username` command to convert them to local users. You have to set an password for the user afterwards. +For security reasons, it is not possible to authenticate via SAML as a local user (and vice versa). So if you have +existing users in your Part-DB database and want them to be able to log in via SAML in the future, you can use +the `php bin/console partdb:user:convert-to-saml-user username` command to convert them to SAML users. This will remove +the password hash from the database and mark them as SAML users, so they can log in via SAML in the future. + +The reverse is also possible: If you have existing SAML users and want them to be able to log in via the Part-DB login +form, you can use the `php bin/console partdb:user:convert-to-saml-user --to-local username` command to convert them to +local users. You have to set a password for the user afterward. {: .important } -> It is recommended that you let the original admin user (ID: 2) be a local user, so you can still login to Part-DB if the SAML identity provider is not available. +> It is recommended that you let the original admin user (ID: 2) be a local user, so you can still login to Part-DB if +> the SAML identity provider is not available. ## Advanced SAML configuration -You can find some more advanced SAML configuration options in the `config/packages/nbgrp_onelogin_saml.yaml` file. Refer to the file for more information. + +You can find some more advanced SAML configuration options in the `config/packages/nbgrp_onelogin_saml.yaml` file. Refer +to the file for more information. Normally you don't have to change anything here. -Please note that this file is not saved by the Part-DB backup tool, so you have to save it manually if you want to keep your changes. On docker containers you have to configure a volume mapping for it. +Please note that this file is not saved by the Part-DB backup tool, so you have to save it manually if you want to keep +your changes. On docker containers you have to configure a volume mapping for it. +## SAML behind a reverse proxy + +If you are running Part-DB behind a reverse proxy, configure the `TRUSTED_PROXIES` environment and other reverse proxy +settings as described in the [reverse proxy guide]({% link installation/reverse-proxy.md %}). +If you want to use SAML you also need to set `SAML_BEHIND_PROXY` to `true` to enable the SAML proxy mode. diff --git a/docs/partkeepr_migration.md b/docs/partkeepr_migration.md index 41a1ff40..e37f8055 100644 --- a/docs/partkeepr_migration.md +++ b/docs/partkeepr_migration.md @@ -11,41 +11,57 @@ nav_order: 101 This guide describes how to migrate from [PartKeepr](https://partkeepr.org/) to Part-DB. -Part-DB has a built-in migration tool, which can be used to migrate the data from an existing PartKeepr instance to -a new Part-DB instance. Most of the data can be migrated, however there are some limitations, you can find below. - +Part-DB has a built-in migration tool, which can be used to migrate the data from an existing PartKeepr instance to +a new Part-DB instance. Most of the data can be migrated, however, there are some limitations, that you can find below. + ## What can be imported -* Datastructures (Categories, Footprints, Storage Locations, Manufacturers, Distributors, Part Measurement Units) -* Basic part informations (Name, Description, Comment, etc.) -* Attachments and images of parts, projects, footprints, manufacturers and storage locations + +* Data structures (Categories, Footprints, Storage Locations, Manufacturers, Distributors, Part Measurement Units) +* Basic part information (Name, Description, Comment, etc.) +* Attachments and images of parts, projects, footprints, manufacturers, and storage locations * Part prices (distributor infos) * Part parameters * Projects (including parts and attachments) -* Users (optional): Passwords however will be not migrated, and need to be reset later +* Users (optional): Passwords however will not be migrated, and need to be reset later ## What can't be imported -* Metaparts (A dummy version of the metapart will be created in Part-DB, however it will not function as metapart) + +* Metaparts (A dummy version of the metapart will be created in Part-DB, however, it will not function as metapart) * Multiple manufacturers per part (only the last manufacturer of a part will be migrated) -* Overage information for project parts (the overage info will be set as comment in the project BOM, but will have no effect) +* Overage information for project parts (the overage info will be set as a comment in the project BOM, but will have no + effect) * Batch Jobs * Parameter Units (the units will be written into the parameters) * Project Reports and Project Runs -* Stock history +* Stock History * Any kind of PartKeepr preferences ## How to migrate -1. Install Part-DB like described in the installation guide. You can use any database backend you want (mysql or sqlite). Run the database migration, but do not create any new data yet. -2. Export your PartKeepr database as XML file using [mysqldump](https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html): -When the MySQL database is running on the local computer and you are root you can just run the command `mysqldump --xml PARTKEEPR_DATABASE --result-file pk.xml`. -If your server is remote or your MySQL authentication is different, you need to run `mysqldump --xml -h PARTKEEPR_HOST -u PARTKEEPR_USER -p PARTKEEPR_DATABASE`, where you replace `PARTKEEPR_HOST` -with the hostname of your MySQL database and `PARTKEEPR_USER` with the username of MySQL user which has access to the PartKeepr database. You will be asked for the MySQL user password. -3. Go the Part-DB main folder and run the command `php bin/console partdb:migrations:import-partkeepr path/to/pk.xml`. This step will delete all existing data in the Part-DB database and import the contents of PartKeepr. -4. Copy the contents of `data/files/` from your PartKeepr installation to the `uploads/` folder of your Part-DB installation and the contents of `data/images` from PartKeepr to `public/media/` of Part-DB. + +1. Install Part-DB as described in the installation guide. You can use any database backend you want (MySQL or + SQLite). Run the database migration, but do not create any new data yet. +2. Export your PartKeepr database as XML file using [mysqldump](https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html): + When the MySQL database is running on the local computer, and you are root you can just run the + command `mysqldump --xml PARTKEEPR_DATABASE --result-file pk.xml`. + If your server is remote or your MySQL authentication is different, you need to + run `mysqldump --xml -h PARTKEEPR_HOST -u PARTKEEPR_USER -p PARTKEEPR_DATABASE`, where you replace `PARTKEEPR_HOST` + with the hostname of your MySQL database and `PARTKEEPR_USER` with the username of MySQL user which has access to the + PartKeepr database. You will be asked for the MySQL user password. +3. Go to the Part-DB main folder and run the command `php bin/console partdb:migrations:import-partkeepr path/to/pk.xml`. + This step will delete all existing data in the Part-DB database and import the contents of PartKeepr. +4. Copy the contents of `data/files/` from your PartKeepr installation to the `uploads/` folder of your Part-DB + installation and the contents of `data/images` from PartKeepr to `public/media/` of Part-DB. 5. Clear the cache of Part-DB by running: `php bin/console cache:clear` -6. Go to the Part-DB web interface. You can login with the username `admin` and the password, which is shown during the installation process of Part-DB (step 1). You should be able to see all the data from PartKeepr. +6. Go to the Part-DB web interface. You can log in with the username `admin` and the password, which is shown during the + installation process of Part-DB (step 1). You should be able to see all the data from PartKeepr. ## Import users -If you want to import the users (mostly the username and email address) from PartKeepr, you can add the `--import-users` option on the database import command (step 3): `php bin/console partdb:migrations:import-partkeepr --import-users path/to/pk.xml`. -All imported users of PartKeepr will be assigned to a new group "PartKeepr Users", which has normal user permissions (so editing data, but no administrative tasks). You can change the group and permissions later in Part-DB users managment. -Passwords can not be imported from PartKeepr and all imported users get marked as disabled user. So to allow users to login, you need to enable them in the user management and assign a password. \ No newline at end of file +If you want to import the users (mostly the username and email address) from PartKeepr, you can add the `--import-users` +option on the database import command (step 3): +`php bin/console partdb:migrations:import-partkeepr --import-users path/to/pk.xml`. + +All imported users of PartKeepr will be assigned to a new group "PartKeepr Users", which has normal user permissions (so +editing data, but no administrative tasks). You can change the group and permissions later in Part-DB users management. +Passwords can not be imported from PartKeepr and all imported users get marked as disabled. So to allow users to +log in, you need to enable them in the user management and assign a password. \ No newline at end of file diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 9441d9b9..a5b1b1c8 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -4,40 +4,67 @@ title: Troubleshooting --- # Troubleshooting + Sometimes things go wrong and Part-DB shows an error message. This page should help you to solve the problem. ## Error messages -When a common, easy fixable error occurs (like a non up-to-date database), Part-DB will show you some short instructions on how to fix the problem. If you have a problem that is not listed here, please open an issue on GitHub. + +When a common, easily fixable error occurs (like a non-up-to-date database), Part-DB will show you some short instructions +on how to fix the problem. If you have a problem that is not listed here, please open an issue on GitHub. ## General procedure + If you encounter an error, try the following steps: -* Clear cache of Part-DB with the console command: + +* Clear the cache of Part-DB with the console command: + ```bash php bin/console cache:clear ``` -* Check if the database needs an update (and perform it when needed) with the console command: + +* Check if the database needs an update (and perform it when needed) with the console command: + ```bash php bin/console doctrine:migrations:migrate ``` -If this does not help, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-symfony). +If this does not help, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-server). + +## Search for a user and reset the password -## Search for user and reset password: You can list all users with the following command: `php bin/console partdb:users:list` -To reset the password of a user you can use the following command: `php bin/console partdb:users:set-password [username]` - +To reset the password of a user you can use the following +command: `php bin/console partdb:users:set-password [username]` ## Error logs + Detailed error logs can be found in the `var/log` directory. When Part-DB is installed directly, the errors are written to the `var/log/prod.log` file. -When Part-DB is installed with Docker, the errors are written directly to the console output. +When Part-DB is installed with Docker, the errors are written directly to the console output. You can see the logs with the following command, when you are in the folder with the `docker-compose.yml` file + ```bash docker-compose logs -f ``` Please include the error logs in your issue on GitHub, if you open an issue. +## KiCad Integration Issues + +### "API responded with error code: 0: Unknown" + +If you get this error when trying to connect KiCad to Part-DB, it is most likely caused by KiCad not trusting your SSL/TLS certificate. + +**Cause:** KiCad does not trust self-signed SSL/TLS certificates. + +**Solutions:** +- Use HTTP instead of HTTPS for the `root_url` in your KiCad library configuration (only recommended for local networks) +- Use a certificate from a trusted Certificate Authority (CA) like [Let's Encrypt](https://letsencrypt.org/) +- Add your self-signed certificate to the system's trusted certificate store on the computer running KiCad (the exact steps depend on your operating system) + +For more information about KiCad integration, see the [EDA / KiCad integration](../usage/eda_integration.md) documentation. + ## Report Issue -If an error occurs, or you found a bug, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-symfony). + +If an error occurs, or you found a bug, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-server). diff --git a/docs/upgrade/1_to_2.md b/docs/upgrade/1_to_2.md new file mode 100644 index 00000000..ef0f4575 --- /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 migrating 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 www-data` where necessary.** + +1. Make a backup of your existing Part-DB installation, including the database, data directories and the configuration files and `.env.local` file. +The `php bin/console partdb:backup` command can help you with this. +2. Pull the v2 version. For git installation you can do this with `git checkout v2.0.0` (or newer version) +3. Remove the `var/cache/` directory inside the Part-DB installation to ensure that no old cache files remain. +4. Run `composer install --no-dev -o` to update the dependencies. +5. Run `yarn install` and `yarn build` to update the frontend assets. +6. Run `php bin/console doctrine:migrations:migrate` to update the database schema. +7. Clear the cache with `php bin/console cache:clear`. +8. Open your Part-DB instance in the browser and log in as an admin user. +9. Go to the user or group permissions page, and give yourself (and other administrators) the right to change system settings (under "System" and "Configuration"). +10. You can now go to the settings page (under "System" and "Settings") and check if all settings are correct. +11. Parameters which were previously set via environment variables are greyed out and cannot be changed in the web interface. +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 +losing 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 00000000..4462f0dd --- /dev/null +++ b/docs/upgrade/index.md @@ -0,0 +1,11 @@ +--- +layout: default +title: Upgrade +nav_order: 7 +has_children: true +--- + +# Upgrade + +This section provides information on how to upgrade Part-DB to the latest version. +This is intended for major release upgrades, where requirements or things change significantly. diff --git a/docs/upgrade/upgrade_legacy.md b/docs/upgrade/upgrade_legacy.md new file mode 100644 index 00000000..b83661f3 --- /dev/null +++ b/docs/upgrade/upgrade_legacy.md @@ -0,0 +1,92 @@ +--- +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 + +Part-DB 1.0 was a complete rewrite of the old Part-DB (< 1.0.0), which you can +find [here](https://github.com/Part-DB/Part-DB). A lot of things changed internally, but Part-DB was always developed +with compatibility in mind, so you can migrate smoothly to the new Part-DB version, and utilize its new features and +improvements. + +Some things changed however to the old version and some features are still missing, so be sure to read the following +sections carefully before proceeding to upgrade. + +## Changes + +* 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. + It is possible to migrate your existing BBCode to Markdown + via `php bin/console partdb:migrations:convert-bbcode`. +* Server exceptions are not logged into event log anymore. For security reasons (exceptions can contain sensitive + information) exceptions are only logged to server log (by default under './var/log'), so only the server admins can access it. +* Profile labels are now saved in the database (before they were saved in a separate JSON file). **The profiles of legacy + Part-DB versions can not be imported into new Part-DB 1.0** +* Label placeholders now use the `[[PLACEHOLDER]]` format instead of `%PLACEHOLDER%`. Also, some placeholders have + changed. +* Configuration is now done via configuration files/environment variables instead of the WebUI (this may change in + the future). +* Database updates are now done via console instead of the WebUI +* Permission system changed: **You will have to newly set the permissions of all users and groups!** +* Import / Export file format changed. Fields must be English now (unlike in legacy Part-DB versions, where German + fields in CSV were possible) + and you may have to change the header line/field names of your CSV files. + +## Missing features + +* No possibility of marking parts for ordering (yet) +* No support for 3D models of footprints (yet) +* No possibility to disable footprints, manufacturers globally (or per category). This should not have a big impact + when you forbid users to edit/create them. +* No resistor calculator or SMD label tools + +## Upgrade process + +{: .warning } +> Once you have upgraded the database to the latest version, you will not be able to access the database with Part-DB +> 0.5.*. Doing so could lead to data corruption. So make a backup before you proceed the upgrade, so you will be able to +> revert the upgrade, when you are not happy with the new version +> +> Beware that all user and group permissions will be reset, and you have to set the permissions again +> the new Part-DB as many permissions changed, and automatic migration is not possible. + +1. Upgrade your existing Part-DB version the newest Part-DB 0.5.* version (at the moment Part-DB 0.5.8), as described + in the old Part-DB's repository. +2. Make a backup of your database and attachments. If something goes wrong during migration, you can use this backup to + start over. If you have some more complex permission configuration, you maybe want to do screenshots of it, so you + can redo it again later. +3. Set up the new Part-DB as described in the installation section. You will need to do the setup for a MySQL instance ( + either via docker or direct installation). Set the `DATABASE_URL` environment variable in your `.env.local` ( + or `docker-compose.yaml`) to your existing database. ( + e.g. `DATABASE_URL=mysql://PARTDB_USER:PASSWORD@localhost:3306/DATABASE_NAME`) +4. Ensure that the correct base currency is configured (`BASE_CURRENCY` env), this must match the currency used in the + old Part-DB version. If you used Euro, you do not need to change anything. +5. Run `php bin/console cache:clear` and `php bin/console doctrine:migrations:migrate`. +6. Run `php bin/console partdb:migrations:convert-bbcode` to convert the BBCode used in comments and part description to + the newly used markdown. +7. Copy the content of the `data/media` folder from the old Part-DB instance into `public/media` folder in the new + version. +8. Run `php bin/console cache:clear` +9. You should be able to log in to Part-DB now using your admin account and the old password. If you do not know the + admin username, run `php bin/console partdb:users:list` and look for the user with ID 1. You can reset the password + of this user using `php bin/console partdb:users:set-password [username]`. +10. All other users besides the admin user are disabled (meaning they can not log in). Go to "System->User" and "System-> + Group" and check the permissions of the users (and change them if needed). If you are done enable the users again, by + removing the disabled checkmark in the password section. If you have a lot of users you can enable them all at once + using `php bin/console partdb:users:enable --all` + +**It is not possible to access the database using the old Part-DB version. +If you do so, this could damage your database.** Therefore, it is recommended to remove the old Part-DB version, after +everything works. + +## Issues + +If you encounter any issues (especially during the database migration) or features do not work like intended, please +open an issue ticket at GitHub. diff --git a/docs/upgrade_legacy.md b/docs/upgrade_legacy.md deleted file mode 100644 index d43fa2ed..00000000 --- a/docs/upgrade_legacy.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -layout: default -title: Upgrade from legacy Part-DB version (<1.0) -nav_order: 100 ---- - -# Upgrade from legacy Part-DB version - -Part-DB 1.0 was a complete rewrite of the old Part-DB (< 1.0.0), which you can find [here](https://github.com/Part-DB/Part-DB). A lot of things changed internally, but Part-DB was always developed with compatibility in mind, so you can migrate smoothly to the new Part-DB version, and utilize its new features and improvements. - -Some things changed however to the old version and some features are still missing, so be sure to read the following sections carefully before proceeding to upgrade. - -## Changes -* PHP 7.4 or higher is required now (Part-DB 0.5 required PHP 5.4+, Part-DB 0.6 PHP 7.0). - PHP 7.4 (or newer) is shipped by all current major Linux distros now (and can be installed by third party sources on others), - Releases are available for Windows too, so almost everybody should be able to use PHP 7.4 -* **Console access highly required.** The installation of composer and frontend dependencies require console access, also more sensitive stuff like database migration work via CLI now, so you should have console access on your server. -* Markdown/HTML is now used instead of BBCode for rich text in description and command fields. - It is possible to migrate your existing BBCode to Markdown via `php bin/console php bin/console partdb:migrations:convert-bbcode`. -* Server exceptions are not logged to Event log anymore. For security reasons (exceptions can contain sensitive informations) - exceptions are only logged to server log (by default under './var/log'), so only the server admins can access it. -* Profile labels are now saved in Database (before they were saved in a seperate JSON file). **The profiles of legacy Part-DB versions can not be imported into new Part-DB 1.0** -* Label placeholders now use the `[[PLACEHOLDER]]` format instead of `%PLACEHOLDER%`. Also some placeholders has changed. -* Configuration is now done via configuration files / environment variables instead of the WebUI (this maybe change in the future). -* Database updated are now done via console instead of the WebUI -* Permission system changed: **You will have to newly set the permissions of all users and groups!** -* Import / Export file format changed. Fields must be english now (unlike in legacy Part-DB versions, where german fields in CSV were possible) -and you maybe have to change the header line/field names of your CSV files. - -## Missing features -* No possibility to mark parts for ordering (yet) -* No support for 3D models of footprints (yet) -* No possibility to disable footprints, manufacturers globally (or per category). This should not have a big impact, when you forbid users to edit/create them. -* No resistor calculator or SMD labels tools - -## Upgrade process - -{: .warning } -> Once you have upgraded the database to the latest version, you will not be able to access the database with Part-DB 0.5.*. Doing so could lead to data corruption. So make a a backup before you proceed the upgrade, so you will be able to revert the upgrade, when you are not happy with the new version -> -> Beware that all user and group permissions will be reset, and you have to set the permissions again -> the new Part-DB as many permissions changed, and automatic migration is not possible. - - 1. Upgrade your existing Part-DB version the newest Part-DB 0.5.* version (in the moment Part-DB 0.5.8), like described in the old Part-DB's repository. - 2. Make a backup of your database and attachments. If somethings goes wrong during migration, you can use this backup to start over. If you have some more complex permission configuration, you maybe want to do screenshots of it, so you can redo it again later. - 3. Setup the new Part-DB like described in installation section. You will need to do the setup for a MySQL instance (either via docker or direct installation). Set the `DATABASE_URL` environment variable in your `.env.local` (or `docker-compose.yaml`) to your existing database. (e.g. `DATABASE_URL=mysql://PARTDB_USER:PASSWORD@localhost:3306/DATABASE_NAME`) - 4. Ensure that the correct base currency is configured (`BASE_CURRENCY` env), this must match the currency used in the old Part-DB version. If you used Euro, you do not need to change anything. - 5. Run `php bin/console cache:clear` and `php bin/console doctrine:migrations:migrate`. - 4. Run `php bin/console partdb:migrations:convert-bbcode` to convert the BBCode used in comments and part description to the newly used markdown. - 5. Copy the content of the `data/media` folder from the old Part-DB instance into `public/media` folder in the new version. - 6. Run `php bin/console cache:clear` - 7. You should be able to login to Part-DB now using your admin account and the old password. If you do not know the admin username, run `php bin/console partdb:users:list` and look for the user with ID 1. You can reset the password of this user using `php bin/console partdb:users:set-password [username]`. - 8. All other users besides the admin user are disabled (meaning they can not login). Go to "System->User" and "System->Group" and check the permissions of the users (and change them if needed). If you are done enable the users again, by removing the disabled checkmark in the password section. If you have a lot of users you can enable them all at once using `php bin/console partdb:users:enable --all` - -**It is not possible to access the database using the old Part-DB version. -If you do so, this could damage your database.** Therefore it is recommended to remove the old Part-DB version, after everything works. - - -## Issues -If you encounter any issues (especially during the database migration) or features do not work like intended, please open an issue ticket at GitHub. \ No newline at end of file diff --git a/docs/usage/backup_restore.md b/docs/usage/backup_restore.md index edcd1a87..c4444d24 100644 --- a/docs/usage/backup_restore.md +++ b/docs/usage/backup_restore.md @@ -6,48 +6,75 @@ parent: Usage # Backup and Restore Data -When working productively you should backup the data and configuration of Part-DB regularly to prevent data loss. This is also useful, if you want to migrate your Part-DB instance from one server to another. In that case you just have to backup the data on server 1, move the backup to server 2, install Part-DB on server 2 and restore the backup. +When working productively, you should back up the data and configuration of Part-DB regularly to prevent data loss. This +is also useful if you want to migrate your Part-DB instance from one server to another. In that case, you just have to +back up the data on server 1, move the backup to server 2, install Part-DB on server 2, and restore the backup. ## Backup (automatic / Part-DB supported) -Part-DB includes a command `php bin/console partdb:backup` which automatically collects all the needed data (described below) and saves them to a ZIP file. + +Part-DB includes a command `php bin/console partdb:backup` which automatically collects all the needed data (described +below) and saves them to a ZIP file. If you are using a MySQL/MariaDB database you need to have `mysqldump` installed and added to your `$PATH` env. ### Usage -To backup all possible data, run the following command: `php bin/console partdb:backup --full /path/to/backup/partdb_backup.zip`. -It is possible to do only partial backups (config, attachments, or database). See `php bin/console partdb:backup --help` for more infos about these options. +To back up all possible data, run the following +command: `php bin/console partdb:backup --full /path/to/backup/partdb_backup.zip`. + +It is possible to do only partial backups (config, attachments, or database). See `php bin/console partdb:backup --help` +for more info about these options. ## Backup (manual) -There are 3 parts which have to be backup-ed: The configuration files, which contains the instance specific options, the uploaded files of attachments, and the database containing the most data of Part-DB. + +Three parts have to be backed up: The configuration files, which contain the instance-specific options, the +uploaded files of attachments, and the database containing the most data of Part-DB. Everything else like thumbnails and cache files, are recreated automatically when needed. ### Configuration files -You have to copy the `.env.local` file and (if you have changed it) the `config/parameters.yaml` and `config/banner.md` to your backup location. + +You have to copy the `.env.local` file and (if you have changed it) the `config/parameters.yaml` and `config/banner.md` +to your backup location. ### Attachment files + You have to recursively copy the `uploads/` folder and the `public/media` folder to your backup location. ### Database -#### Sqlite -If you are using sqlite, it is sufficient to just copy your `app.db` from your database location (normally `var/app.db`) to your backup location. + +#### SQLite + +If you are using SQLite, it is sufficient to just copy your `app.db` from your database location (normally `var/app.db`) +to your backup location. #### MySQL / MariaDB -For MySQL / MariaDB you have to dump the database to an SQL file. You can do this manually with phpmyadmin, or you use [`mysqldump`](https://mariadb.com/kb/en/mariadb-dumpmysqldump/) to dump the database to an SQL file via command line interface (`mysqldump -uBACKUP -pPASSWORD DATABASE`) + +For MySQL / MariaDB you have to dump the database to an SQL file. You can do this manually with phpmyadmin, or you +use [`mysqldump`](https://mariadb.com/kb/en/mariadb-dumpmysqldump/) to dump the database to an SQL file via command line +interface (`mysqldump -uBACKUP -pPASSWORD DATABASE`) ## Restore -Install Part-DB as usual as described in the installation section, with the exception of the database creation / migration part. You have to use the same database type (sqlite or mysql) as on the back-uped server instance. + +Install Part-DB as usual as described in the installation section, except for the database creation/migration part. You +have to use the same database type (SQLite or MySQL) as on the backed up server instance. ### Restore configuration -Copy configuration files `.env.local`, (and if existing) `config/parameters.yaml` and `config/banner.md` from the backup to your new Part-DB instance and overwrite the existing files there. + +Copy configuration files `.env.local`, (and if existing) `config/parameters.yaml` and `config/banner.md` from the backup +to your new Part-DB instance and overwrite the existing files there. ### Restore attachment files + Copy the `uploads/` and the `public/media/` folder from your backup into your new Part-DB folder. ### Restore database -#### Sqlite -Copy the backup-ed `app.db` into the database folder normally `var/app.db` in Part-DB root folder. + +#### SQLite + +Copy the backed up `app.db` into the database folder normally `var/app.db` in Part-DB root folder. #### MySQL / MariaDB -Recreate a database and user with the same credentials as before (or update the database credentials in the `.env.local` file). + +Recreate a database and user with the same credentials as before (or update the database credentials in the `.env.local` +file). Import the dumped SQL file from the backup into your new database. \ No newline at end of file diff --git a/docs/usage/bom_import.md b/docs/usage/bom_import.md index 86e590d7..b4bcb2be 100644 --- a/docs/usage/bom_import.md +++ b/docs/usage/bom_import.md @@ -7,23 +7,39 @@ parent: Usage # Import Bill of Material (BOM) for Projects -Part-DB supports the import of Bill of Material (BOM) files for projects. This allows you to directly import a BOM file from your ECAD software into your Part-DB project. +Part-DB supports the import of Bill of Material (BOM) files for projects. This allows you to directly import a BOM file +from your ECAD software into your Part-DB project. - -The import process is currently semi-automatic. This means Part-DB will take the BOM file and create entries for all parts in the BOM file in your project and assign fields like -mountnames (e.g. 'C1, C2, C3'), quantity and more. -However, you still have to assign the parts from Part-DB database to the entries (if applicable) after the import by hand, +The import process is currently semi-automatic. This means Part-DB will take the BOM file and create entries for all +parts in the BOM file in your project and assign fields like +mount names (e.g. 'C1, C2, C3'), quantity and more. +However, you still have to assign the parts from Part-DB database to the entries (if applicable) after the import by +hand, as Part-DB can not know which part you had in mind when you designed your schematic. ## Usage + In the project view or edit click on the "Import BOM" button, below the BOM table. This will open a dialog where you can select the BOM file you want to import and some options for the import process: * **Type**: The format/type of the BOM file. See below for explanations of the different types. -* **Clear existing BOM entries before import**: If this is checked, all existing BOM entries, which are currently associated with the project, will be deleted before the import. +* **Clear existing BOM entries before import**: If this is checked, all existing BOM entries, which are currently + associated with the project, will be deleted before the import. ### Supported BOM file formats -* **KiCAD Pcbnew BOM (CSV file)**: A CSV file of the Bill of Material (BOM) generated by [KiCAD Pcbnew](https://www.kicad.org/). -Please note that you have to export the BOM from the PCB editor, the BOM generated by the schematic editor (Eeschema) 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 Pcbnew BOM (CSV file)**: A CSV file of the Bill of Material (BOM) generated + by [KiCAD Pcbnew](https://www.kicad.org/). + Please note that you have to export the BOM from the PCB editor, the BOM generated by the schematic editor (Eeschema) + 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 7a23483a..2ca35ec4 100644 --- a/docs/usage/console_commands.md +++ b/docs/usage/console_commands.md @@ -8,31 +8,74 @@ parent: Usage Part-DB provides some console commands to display various information or perform some tasks. The commands are invoked from the main directory of Part-DB with the command `php bin/console [command]` in the context -of the database user (so usually the webserver user), so you maybe have to use `sudo` or `su` to execute the commands. +of the web server user (so usually the webserver user), so you may have to use `sudo` or `su` to execute the commands: + +```bash +sudo -u www-data php bin/console [command] +``` -You can get help for every command with the parameter `--help`. See `php bin/console` for a list of all available commands. +You can get help for every command with the parameter `--help`. See `php bin/console` for a list of all available +commands. + +If you are running Part-DB in a Docker container, you must either execute the commands from a shell inside the container, +or use the `docker exec` command to execute the command directly inside the container. For example, if your Docker container +is named `partdb`, you can execute the command `php bin/console cache:clear` with the following command: + +```bash +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 -## User managment commands * `php bin/console partdb:users:list`: List all users of this Part-DB instance -* `php bin/console partdb:users:set-password [username]`: Set/Changes the password of the user with the given username. This allows administrators to reset a password of a user, if he forgot it. -* `php bin/console partdb:users:enable [username]`: Enable/Disable the user with the given username (use `--disable` to disable the user, which prevents login) +* `php bin/console partdb:users:set-password [username]`: Set/Changes the password of the user with the given username. + This allows administrators to reset a password of a user, if he forgot it. +* `php bin/console partdb:users:enable [username]`: Enable/Disable the user with the given username (use `--disable` to + disable the user, which prevents login) * `php bin/console partdb:users:permissions`: View/Change the permissions of the user with the given username -* `php bin/console partdb:users:upgrade-permissions-schema`: Upgrade the permissions schema of users to the latest version (this is normally automatically done when the user visits a page) +* `php bin/console partdb:users:upgrade-permissions-schema`: Upgrade the permissions schema of users to the latest + version (this is normally automatically done when the user visits a page) * `php bin/console partdb:logs:show`: Show the most recent entries of the Part-DB event log / recent activity -* `php bin/console partdb:user:convert-to-saml-user`: Convert a local user to a SAML/SSO user. This is needed, if you want to use SAML/SSO authentication for a user, which was created before you enabled SAML/SSO authentication. +* `php bin/console partdb:user:convert-to-saml-user`: Convert a local user to a SAML/SSO user. This is needed, if you + want to use SAML/SSO authentication for a user, which was created before you enabled SAML/SSO authentication. ## Currency commands -* `php bin/console partdb:currencies:update-exchange-rates`: Update the exchange rates of all currencies from the internet) + +* `php bin/console partdb:currencies:update-exchange-rates`: Update the exchange rates of all currencies from the + internet ## Installation/Maintenance commands + * `php bin/console partdb:backup`: Backup the database and the attachments * `php bin/console partdb:version`: Display the current version of Part-DB and the used PHP version -* `php bin/console partdb:check-requirements`: Check if the requirements for Part-DB are met (PHP version, PHP extensions, etc.) and make suggestions what could be improved -* `partdb:migrations:convert-bbcode`: Migrate the old BBCode markup codes used in legacy Part-DB versions (< 1.0.0) to the new markdown syntax -* `partdb:attachments:clean-unused`: Remove all attachments which are not used by any database entry (e.g. orphaned attachments) -* `partdb:cache:clear`: Clears all caches, so the next page load will be slower, but the cache will be rebuild. This can maybe fix some issues, when the cache were corrupted. This command is also needed after changing things in the `parameters.yaml` file or upgrading Part-DB. -* `partdb:migrations:import-partkeepr`: Imports an 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!* +* `php bin/console partdb:check-requirements`: Check if the requirements for Part-DB are met (PHP version, PHP + extensions, etc.) and make suggestions what could be improved +* `partdb:migrations:convert-bbcode`: Migrate the old BBCode markup codes used in legacy Part-DB versions (< 1.0.0) to + the new Markdown syntax +* `partdb:attachments:clean-unused`: Remove all attachments which are not used by any database entry (e.g. orphaned + attachments) +* `partdb:cache:clear`: Clears all caches, so the next page load will be slower, but the cache will be rebuilt. This can + maybe fix some issues when the cache was corrupted. This command is also needed after changing things in + the `parameters.yaml` file or upgrading Part-DB. +* `partdb:migrations:import-partkeepr`: Imports a mysqldump XML dump of a PartKeepr database into Part-DB. This is only + needed for users, which want to migrate from PartKeepr to Part-DB. *All existing data in the Part-DB database is + 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 that are not already downloaded to the + local filesystem. This is useful to create local backups of the attachments, no matter what happens on the remote, and + also makes picture thumbnails available for the frontend for them. diff --git a/docs/usage/eda_integration.md b/docs/usage/eda_integration.md new file mode 100644 index 00000000..28386a91 --- /dev/null +++ b/docs/usage/eda_integration.md @@ -0,0 +1,89 @@ +--- +layout: default +title: EDA / KiCad integration +parent: Usage +--- + +# EDA / KiCad integration + +Part-DB can function as a central database for [EDA](https://en.wikipedia.org/wiki/Electronic_design_automation) or ECAD software used to design electronic schematics and PCBs. +You can connect your EDA software and can view your available parts, with the data saved from Part-DB directly in your EDA software. +Part-DB allows to configure additional metadata for the EDA, to associate symbols and footprints for use inside the EDA software, so the part becomes +directly usable inside the EDA software. +This also allows to configure available and usable parts and their properties in a central place, which is especially useful in teams, where multiple persons design PCBs. + +**Currently only KiCad is supported!** + +## KiCad Setup + +{: .important } +> Part-DB uses the HTTP library feature of KiCad, which was experimental in earlier versions. If you want to use this feature, you need to install KiCad 8 or newer. + +Part-DB should be accessible from the PCs with KiCad. The URL should be stable (so no dynamically changing IP). +You require a user account in Part-DB, which has permission to access the Part-DB API and create API tokens. Every user can have their own account, or you set up a shared read-only account. + +{: .warning } +> **HTTPS with Self-Signed Certificates** +> +> KiCad does not trust self-signed SSL/TLS certificates. If your Part-DB instance uses HTTPS with a self-signed certificate, KiCad will fail to connect and show an error like: `API responded with error code: 0: Unknown`. +> +> To resolve this issue, you have the following options: +> - Use HTTP instead of HTTPS for the `root_url` (only recommended for local networks) +> - Use a certificate from a trusted Certificate Authority (CA) like [Let's Encrypt](https://letsencrypt.org/) +> - Add your self-signed certificate to the system's trusted certificate store on the computer running KiCad (the exact steps depend on your operating system) + +To connect KiCad with Part-DB do the following steps: + +1. Create an API token on the user settings page for the KiCad application and copy/save it when it is shown. Currently, KiCad can only read the Part-DB database, so a token with a read-only scope is enough. +2. Add some EDA metadata to parts, categories, or footprints. Only parts with usable info will show up in KiCad. See below for more info. +3. Create a file `partd.kicad_httplib` (or similar, only the extension is important) with the following content: +``` +{ + "meta": { + "version": 1.0 + }, + "name": "Part-DB library", + "description": "This KiCAD library fetches information externally from ", + "source": { + "type": "REST_API", + "api_version": "v1", + "root_url": "http://kicad-instance.invalid/en/kicad-api/", + "token": "THE_GENERATED_API_TOKEN" + } +} +``` +4. Replace the `root_url` with the URL of your Part-DB instance plus `/en/kicad-api/`. You can find the right value for this in the Part-DB user settings page under "API endpoints" in the "API tokens" panel. +5. Replace the `token` field value with the token you have generated in step 1. +6. Open KiCad and add this created file as a library in the KiCad symbol table under (Preferences --> Manage Symbol Libraries) + +If you then place a new part, the library dialog opens, and you should be able to see the categories and parts from Part-DB. + +### How to associate footprints and symbols with parts + +Part-DB doesn't save any concrete footprints or symbols for the part. Instead, Part-DB just contains a reference string in the part metadata, which points to a symbol/footprint in KiCad's local library. + +You can define this on a per-part basis using the KiCad symbol and KiCad footprint field in the EDA tab of the part editor. Or you can define it at a category (symbol) or footprint level, to assign this value to all parts with this category and footprint. + +For example, to configure the values for a BC547 transistor you would put `Transistor_BJT:BC547` in the part's KiCad symbol field to give it the right schematic symbol in Eeschema and `Package_TO_SOT_THT:TO-92` to give it the right footprint in Pcbnew. + +If you type in a character, you will get an autocomplete list of all symbols and footprints available in the KiCad standard library. You can also input your own value. + +### Parts and category visibility + +Only parts and their categories on which there is any kind of EDA metadata defined show up in KiCad. So if you want to see parts in KiCad, +you need to define at least a symbol, footprint, reference prefix, or value on a part, category or footprint. + +You can use the "Force visibility" checkbox on a part or category to override this behavior and force parts to be visible or hidden in KiCad. + +*Please note that KiCad caches the library categories. So if you change something that would change the visible categories in KiCad, you have to reload Eeschema to see the changes.* + +### Category depth in KiCad + +For performance reasons, only the most top-level categories of Part-DB are shown as categories in KiCad. All parts in the subcategories are shown in the top-level category. + +You can configure the depth of the categories shown in KiCad, via the `EDA_KICAD_CATEGORY_DEPTH` env option. The default value is 0, which means only the top-level categories are shown. +To show more levels of categories, you can set this value to a higher number. + +If you set this value to -1, all parts are shown inside a single category in KiCad, without any subcategories. + +You can view the "real" category path of a part in the part details dialog in KiCad. diff --git a/docs/usage/getting_started.md b/docs/usage/getting_started.md index bc25123f..8772130c 100644 --- a/docs/usage/getting_started.md +++ b/docs/usage/getting_started.md @@ -6,112 +6,154 @@ nav_order: 4 # Getting started -After Part-DB you should begin with customizing the settings, and setting up the basic structures. -Before starting its useful to read a bit about the [concepts of Part-DB]({% link concepts.md %}). +After installing Part-DB, you should begin with customizing the settings and setting up the basic structures. +Before starting, it's useful to read a bit about the [concepts of Part-DB]({% link concepts.md %}). 1. TOC {: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 your `docker-compose.yaml` file. -A list of possible configuration options, can be found [here]({% link configuration.md %}). +Before starting creating data structures, you should check the system settings to ensure that they fit your needs. +After logging in as an administrator, you can find the settings in the sidebar under `Tools -> System -> Settings`. +![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 %}). ## Change password, Set up Two-Factor-Authentication & Customize User settings -If you have not already done, you should change your user password. You can do this in the user settings (available in the navigation bar drop down with the user symbol). +If you have not already done so, you should change your user password. You can do this in the user settings (available in +the navigation bar drop-down with the user symbol). ![image]({% link assets/getting_started/change_password.png %}) -There you can also find the option, to set up Two Factor Authentication methods like Google Authenticator. Using this is highly recommended (especially if you have admin permissions) to increase the security of your account. (Two Factor Authentication even can be enforced for all members of a user group) +There you can also find the option to set up Two-Factor Authentication methods like Google Authenticator. Using this is +highly recommended (especially if you have admin permissions) to increase the security of your account. (Two-factor authentication +can even be enforced for all members of a user group) -In the user settings panel you can change account infos like your username, your first and last name (which will be shown alongside your username to identify you better), department information and your email address. The email address is used to send password reset mails, if your system is configured to use this. +In the user settings panel, you can change account info like your username, your first and last name (which will be +shown alongside your username to identify you better), department information, and your email address. The email address +is used to send password reset mails if your system is configured to use this. ![image]({% link assets/getting_started/user_settings.png %}) -In the configuration tab you can also override global settings, like your preferred UI language (which will automatically be applied after login), the timezone you are in (and in which times will be shown for you), your preferred currency (all money values will be shown converted to this to you, if possible) and the theme that should be used. +In the configuration tab you can also override global settings, like your preferred UI language (which will +automatically be applied after login), the timezone you are in (and in which times will be shown for you), your +preferred currency (all money values will be shown converted to this to you, if possible) and the theme that should be +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. -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$$ +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$$ ## Create groups, users and customize permissions ### Users -When logged in as administrator, you can open the users menu in the `Tools` section of the sidebar under `System -> Users`. -At this page you can create new users, change their passwords and settings and change their permissions. -For each user which should use Part-DB you should setup a own account, so that tracking of what user did what works properly. +When logged in as administrator, you can open the users menu in the `Tools` section of the sidebar +under `System -> Users`. +On this page you can create new users, change their passwords and settings, and change their permissions. +For each user who should use Part-DB, you should set up their own account so that tracking of what each user did works +properly. ![image]({% link assets/getting_started/user_admin.png %}) - -You should check the permissions for every user and ensure that they are in the intended way, and no user has more permissions than he needs. -For each capability you can choose between allow, forbid and inherit. In the last case, the permission is determined by the group a user has (if no group is chosen, it equals forbid) +You should check the permissions for every user and ensure that they are in the intended way, and no user has more +permissions than he needs. +For each capability, you can choose between allow, forbid, and inherit. In the last case, the permission is determined by +the group a user has (if no group is chosen, it equals forbid) ![image]({% link assets/getting_started/user_permissions.png %}) - ### Anonymous user -The `anonymous` user is special, as its settings and permissions are used for everybody who is not logged in. By default the anonymous user has read capabilities for your parts. If your Part-DB instance is publicly available you maybe want to restrict the permissions. +The `anonymous` user is special, as its settings and permissions are used for everybody who is not logged in. By default, +the anonymous user has read capabilities for your parts. If your Part-DB instance is publicly available you maybe want +to restrict the permissions. ### Groups -If you have many users which should share the same permissions, it is useful to define the permissions using user groups, which you can create and edit in the `System -> Groups` menu. +If you have many users who should share the same permissions, it is useful to define the permissions using user +groups, which you can create and edit in the `System -> Groups` menu. -By default 3 groups are defined: -* `readonly` which users have only have read permissions (like viewing, searching parts, attachments, etc.) +By default, 3 groups are defined: + +* `readonly` which users only have read permissions (like viewing, searching parts, attachments, etc.) * `users` which users also have rights to edit/delete/create elements -* `admin` which users can do administrative operations (like creating new users, show global system log, etc.) +* `admin` which users can do administrative operations (like creating new users, showing global system log, etc.) -Users only use the setting of a capability from a group, if the user has a group associated and the capability on the user is set to `inherit` (which is the default if creating a new user). You can override the permissions settings of a group per user by explicitly settings the permission at the user. - -Groups are organized as trees, meaning a group can have parent and child permissions and child groups can inherit permissions from their parents. -To inherit the permissions from a parent group set the capability to inherit, otherwise set it explicitly to override the parents permission. +Users only use the setting of a capability from a group, if the user has a group associated and the capability on the +user is set to `inherit` (which is the default if creating a new user). You can override the permissions settings of a +group per user by explicitly setting the permission of the user. +Groups are organized as trees, meaning a group can have parent and child permissions and child groups can inherit +permissions from their parents. +To inherit the permissions from a parent group set the capability to inherit, otherwise, set it explicitly to override +the parents' permission. ## Create Attachment types -Every attachment (that is an file associated with a part, data structure, etc.) must have an attachment type. They can be used to group attachments logically, like differentiating between datasheets, pictures and other documents. +Every attachment (that is a file associated with a part, data structure, etc.) must have an attachment type. They can +be used to group attachments logically, like differentiating between datasheets, pictures, and other documents. You can create/edit attachment types in the tools sidebar under "Edit -> Attachment types": ![image]({% link assets/getting_started/attachment_type_admin.png %}) - -Depending on your usecase different entries here make sense. For part mananagment the following (additional) entries maybe make sense: + +Depending on your use case different entries here make sense. For part management the following (additional) entries may make sense: * Datasheets (restricted to pdfs, Allowed filetypes: `application/pdf`) * Pictures (for generic pictures of components, storage locations, etc., Allowed filetypes: `image/*` -For every attachment type a list of allowed file types, which can be uploaded to an attachment with this attachment type, can be defined. You can either pass a list of allowed file extensions (e.g. `.pdf, .zip, .docx`) and/or a list of [Mime Types](https://en.wikipedia.org/wiki/Media_type) (e.g. `application/pdf, image/jpeg`) or a combination of both here. To allow all browser supported images, you can use `image/*` wildcard here. +For every attachment type a list of allowed file types, which can be uploaded to an attachment with this attachment +type, can be defined. You can either pass a list of allowed file extensions (e.g. `.pdf, .zip, .docx`) and/or a list +of [Mime Types](https://en.wikipedia.org/wiki/Media_type) (e.g. `application/pdf, image/jpeg`) or a combination of both +here. To allow all browser-supported images, you can use `image/*` wildcard here. ## (Optional) Create Currencies -If you want to save priceinformations for parts in a currency different to your global currency (by default Euro), you have to define the additional currencies you want to use under `Edit -> Currencies`: +If you want to save price information for parts in a currency different from your global currency (by default Euro), you +have to define the additional currencies you want to use under `Edit -> Currencies`: ![image]({% link assets/getting_started/currencies_admin.png %}) -You create a new currency, name it however you want (it is recommended to use the official name of the currency) and select the currency ISO code from the list and save it. The currency symbol is determined automatically from chose ISO code. -You can define a exchange rate in terms of your base currency (e.g. how much euros is one unit of your currency worth) to convert the currencies values in your preferred display currency automatically. - +You create a new currency, name it however you want (it is recommended to use the official name of the currency), +select the currency ISO code from the list, and save it. The currency symbol is determined automatically from the chosen ISO +code. +You can define an exchange rate in terms of your base currency (e.g. how many euros is one unit of your currency worth) +to convert the currency values in your preferred display currency automatically. ## (Optional) Create Measurement Units -By default Part-DB assumes that the parts in inventory can be counted by individual indivisible pieces, like LEDs in a box or books in a shelf. -However if you want to manage things, that are divisible and and the instock is described by a physical quantity, like length for cables, or volumina of a liquid, you have to define additional measurement units. +By default, Part-DB assumes that the parts in inventory can be counted by individual indivisible pieces, like LEDs in a +box or books on a shelf. +However, if you want to manage things, that are divisible and the stock is described by a physical quantity, like +length for cables, or volumes of a liquid, you have to define additional measurement units. This is possible under `Edit -> Measurement Units`: ![image]({% link assets/getting_started/units_admin.png %}) -You can give the measurement unit a name and an optional unit symbol (like `m` for meters) which is shown when quantities in this unit are displayed. The option `Use SI prefix` is useful for almost all physical quantities, as big and small numbers are automatically formatted with SI-prefixes (like 1.5kg instead 1500 grams). +You can give the measurement unit a name and an optional unit symbol (like `m` for meters) which is shown when +quantities in this unit are displayed. The option `Use SI prefix` is useful for almost all physical quantities, as big +and small numbers are automatically formatted with SI prefixes (like 1.5kg instead 1500 grams). -The measurement unit can be selected for each part individually, by setting the option in the advanced tab of a part`s edit menu. +The measurement unit can be selected for each part individually, by setting the option in the advanced tab of a part`s +edit menu. ## Create Categories -A category is used to group parts logically by their function (e.g. all NPN transistors would be put in a "NPN-Transistors" category). +A category is used to group parts logically by their function (e.g. all NPN transistors would be put in a " +NPN-Transistors" category). Categories are hierarchical structures meaning that you can create logical trees to group categories together. See [Concepts]({% link concepts.md %}) for an example tree structure. @@ -121,43 +163,51 @@ Every part has to be assigned to a category, so you should create at least one c ## (Optional) Create Footprints -Footprints are used to describe the physical shape of a part, like a resistor or a capacitor. -They can be used to group parts by their physical shape and to find parts with in the same package. +Footprints are used to describe the physical shape of a part, like a resistor or a capacitor. +They can be used to group parts by their physical shape and to find parts within the same package. You can create/edit footprints in the tools sidebar under "Edit -> Footprints". -It is useful to create footprints for the most common packages, like SMD resistors, capacitors, etc. to make it easier to find parts with the same footprint. -You should create these as a tree structure, so that you can group footprints by their type. +It is useful to create footprints for the most common packages, like SMD resistors, capacitors, etc. to make it easier +to find parts with the same footprint. +You should create these as a tree structure so that you can group footprints by their type. See [Concepts]({% link concepts.md %}) for an example tree structure. -You can define attachments here which are associated with the footprint. The attachment set as preview image, will be +You can define attachments here which are associated with the footprint. The attachment set as the preview image, will be used whenever a visual representation of the footprint is needed (e.g. in the part list). -For many common footprints, you can use the built-in footprints, which can be found in the "Builtin footprint image gallery", which you can find in the tools menu. -Type the name of the image you want to use in the URL field of the attachment and select the image from the dropdown menu. +For many common footprints, you can use the built-in footprints, which can be found in the "Builtin footprint image +gallery", which you can find in the "tools" menu. +Type the name of the image you want to use in the URL field of the attachment and select the image from the dropdown +menu. ## (Optional) Create Storage locations -A storelocation represents a place where parts can be stored. +A storage location represents a place where parts can be stored. You can create/edit storage locations in the tools sidebar under "Edit -> Storage locations". ## (Optional) Create Manufacturers and suppliers -You can create/edit [manufacturers]({% link concepts.md %}#manufacturers) and [suppliers]({% link concepts.md %}#suppliers) in the tools sidebar under "Edit -> Manufacturers" and "Edit -> Suppliers". +You can create/edit [manufacturers]({% link concepts.md %}#manufacturers) and [suppliers]({% link concepts.md +%}#suppliers) in the tools sidebar under "Edit -> Manufacturers" and "Edit -> Suppliers". ## Create parts -You are now ready to create your first part. You can do this by clicking either by clicking "Edit -> New Part" in the tools sidebar tree -or by clicking the "Create new Part" above the (empty) part list, after clicking on one of your newly created categories. +You are now ready to create your first part. You can do this by clicking either by clicking "Edit -> New Part" in the +tools sidebar tree +or by clicking the "Create new Part" above the (empty) part list, after clicking on one of your newly created +categories. You will be presented with a form where you can enter the basic information about your part: ![image]({% link assets/getting_started/new_part.png %}) You have to enter at least a name for the part and choose a category for it, the other fields are optional. -However, it is recommended to fill out as much information as possible, as this will make it easier to find the part later. +However, it is recommended to fill out as much information as possible, as this will make it easier to find the part +later. -You can choose from your created 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 +You can choose from your created data structures to add manufacturer information, supplier information, etc. to the part. +You can also create new data structures on the fly if you want to add additional information to the part, by typing the +name of the new data structure in the field and selecting the "New ..." option in the dropdown menu. See [tips]({% link +usage/tips_tricks.md %}) for more information. diff --git a/docs/usage/import_export.md b/docs/usage/import_export.md index 09e4b163..f4d8d91c 100644 --- a/docs/usage/import_export.md +++ b/docs/usage/import_export.md @@ -7,97 +7,158 @@ parent: Usage # Import & Export data -Part-DB offers the possibility to import existing data (parts, datastructures, etc.) from existing datasources into Part-DB. Data can also be exported from Part-DB into various formats. +Part-DB offers the possibility to import existing data (parts, data structures, etc.) from existing data sources into +Part-DB. Data can also be exported from Part-DB into various formats. ## Import {: .note } -> As data import is a very powerful feature and can easily fill up your database with lots of data, import is by default only available for -> administrators. If you want to allow other users to import data, or can not import data, check the permissions of the user. You can enable import for each data structure +> As data import is a very powerful feature and can easily fill up your database with lots of data, import is by default +> only available for +> administrators. If you want to allow other users to import data, or can not import data, check the permissions of the +> user. You can enable import for each data structure > 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 %}). +If you want to import data from PartKeepr you might want to look into the [PartKeepr migration guide]({% link +partkeepr_migration.md %}). ### Import parts -Part-DB supports the import of parts from CSV files and other formats. This can be used to import existing parts from other databases or datasources into Part-DB. The import can be done via the "Tools -> Import parts" page, which you can find in the "Tools" sidebar panel. +Part-DB supports the import of parts from CSV files and other formats. This can be used to import existing parts from +other databases or data sources into Part-DB. The import can be done via the "Tools -> Import parts" page, which you can +find in the "Tools" sidebar panel. {: .important } -> When importing data, the data is immediatley written to database during the import process, when the data is formally valid. -> You will not be able to check the data before it is written to the database, so you should review the data before using the import tool. +> When importing data, the data is immediately written to database during the import process, when the data is formally +> valid. +> You will not be able to check the data before it is written to the database, so you should review the data before +> using the import tool. -You can upload the file which should be imported here and choose various options on how the data should be treated: -* **Format**: By default "auto" is selected here and Part-DB will try to detect the format of the file automatically based on its file extension. If you want to force a specific format or Part-DB can not auto-detect the format, you can select it here. -* **CSV delimiter**: If you upload an CSV file, you can select the delimiter character which is used to separate the columns in the CSV file. Depending on the CSV file, this might be a comma (`,`), semicolon (`;`). -* **Category override**: You can select (or create) a category here, to which all imported parts should be assigned, no matter what was specified in the import file. This can be useful if you want to assign all imports to a certain category or if no category is specified in the data. If you leave this field empty, the category will be determined by the import file (or the export will error, if no category is specified). -* **Mark all imported parts as "Needs review"**: If this is selected, all imported parts will be marked as "Needs review" after the import. This can be useful if you want to review all imported parts before using them. -* **Create unknown datastructures**: If this is selected Part-DB will create new datastructures (like categories, manufacturers, etc.) if no datastructure(s) with the same name and path already exists. If this is not selected, only existing datastructures will be used and if no matching datastrucure is found, the imported parts field will be empty. -* **Path delimiter**: Part-DB allows you to create/select nested datastructures (like categories, manufacturers, etc.) by using a path (e.g. `Category 1->Category 1.1`, which will select/create the `Category 1.1` whose parent is `Category 1`). This path is separated by the path delimiter. If you want to use a different path delimiter than the default one (which is `>`), you can select it here. -* **Abort on validation error**: If this is selected, the import will be aborted if a validation error occurs (e.g. if a required field is empty) for any of the imported parts and validation errors will be shown on top of the page. If this is not selected, the import will continue for the other parts and only the invalid parts will be skipped. +You can upload the file that should be imported here and choose various options on how the data should be treated: -After you have selected the options, you can start the import by clicking the "Import" button. When the import is finished, you will see the results of the import in the lower half of the page. You find a table with the imported parts (including links to them) there. +* **Format**: By default "auto" is selected here and Part-DB will try to detect the format of the file automatically + based on its file extension. If you want to force a specific format or Part-DB can not auto-detect the format, you can + select it here. +* **CSV delimiter**: If you upload a CSV file, you can select the delimiter character which is used to separate the + columns in the CSV file. Depending on the CSV file, this might be a comma (`,`) or semicolon (`;`). +* **Category override**: You can select (or create) a category here, to which all imported parts should be assigned, no + matter what was specified in the import file. This can be useful if you want to assign all imports to a certain + category or if no category is specified in the data. If you leave this field empty, the category will be determined by + the import file (or the export will error, if no category is specified). +* **Mark all imported parts as "Needs review"**: If this is selected, all imported parts will be marked as "Needs + review" after the import. This can be useful if you want to review all imported parts before using them. +* **Create unknown data structures**: If this is selected, Part-DB will create new data structures (like categories, + manufacturers, etc.) if no data structure(s) with the same name and path already exist. If this is not selected, only + existing data structures will be used, and if no matching data structure is found, the imported parts field will be empty. +* **Path delimiter**: Part-DB allows you to create/select nested data structures (like categories, manufacturers, etc.) + by using a path (e.g. `Category 1->Category 1.1`, which will select/create the `Category 1.1` whose parent + is `Category 1`). This path is separated by the path delimiter. If you want to use a different path delimiter than the + default one (which is `>`), you can select it here. +* **Abort on validation error**: If this is selected, the import will be aborted if a validation error occurs (e.g. if a + required field is empty) for any of the imported parts and validation errors will be shown on top of the page. If this + is not selected, the import will continue for the other parts and only the invalid parts will be skipped. + +After you have selected the options, you can start the import by clicking the "Import" button. When the import is +finished, you will see the results of the import in the lower half of the page. You can find a table with the imported +parts (including links to them) there. #### Fields description -For the importing of parts, you can use the following fields which will be imported into each part. Please note that the field names are case sensitive (so `name` is not the same as `Name`). All fields (besides name) are optional, so you can leave them empty or do not include the column in your file. +For the importing of parts, you can use the following fields which will be imported into each part. Please note that the +field names are case-sensitive (so `name` is not the same as `Name`). All fields (besides name) are optional, so you can +leave them empty or do not include the column in your file. * **`name`** (required): The name of the part. This is the only required field, all other fields are optional. * **`description`**: The description of the part, you can use markdown/HTML syntax here for rich text formatting. * **`notes`** or **`comment`**: The notes of the part, you can use markdown/HTML syntax here for rich text formatting. -* **`category`**: The category of the part. This can be a path (e.g. `Category 1->Category 1.1`), which will select/create the `Category 1.1` whose parent is `Category 1`. If you want to use a different path delimiter than the default one (which is `->`), you can select it in the import options. If the category does not exist and the option "Create unknown datastructures" is selected, it will be created. +* **`category`**: The category of the part. This can be a path (e.g. `Category 1->Category 1.1`), which will + select/create the `Category 1.1` whose parent is `Category 1`. If you want to use a different path delimiter than the + default one (which is `->`), you can select it in the import options. If the category does not exist and the option " + Create unknown datastructures" is selected, it will be created. * **`footprint`**: The footprint of the part. Can be a path similar to the category field. * **`favorite`**: If this is set to `1`, the part will be marked as favorite. * **`manufacturer`**: The manufacturer of the part. Can be a path similar to the category field. * **`manufacturer_product_number`** or **`mpn`**: The manufacturer product number of the part. -* **`manufacturer_product_url`: The URL to the product page of the manufacturer of the part. -* **`manufacturing_status`**: The manufacturing status of the part, must be one of the following values: `announced`, `active`, `nrfnd`, `eol`, `discontinued` or left empty. +* **`manufacturer_product_url`**: The URL to the product page of the manufacturer of the part. +* **`manufacturing_status`**: The manufacturing status of the part, must be one of the following + values: `announced`, `active`, `nrfnd`, `eol`, `discontinued` or left empty. * **`needs_review`** or **`needs_review`**: If this is set to `1`, the part will be marked as "needs review". -* **`tags`**: A comma separated list of tags for the part. +* **`tags`**: A comma-separated list of tags for the part. * **`mass`**: The mass of the part in grams. * **`ipn`**: The IPN (Item Part Number) of the part. * **`minamount`**: The minimum amount of the part which should be in stock. * **`partUnit`**: The measurement unit of the part to use. Can be a path similar to the category field. -With the following fields you can specify storage locations and amount / quantiy in stock of the part. An PartLot will be created automatically from the data and assigned to the part. The following fields are helpers for an easy import for parts at one storage location. If you need to create a Part with multiple PartLots you have to use JSON format (or CSV) with nested objects: +With the following fields, you can specify storage locations and amount/quantity in stock of the part. A PartLot will +be created automatically from the data and assigned to the part. The following fields are helpers for an easy import of +parts at one storage location. If you need to create a Part with multiple PartLots you have to use JSON format (or CSV) +with nested objects: -**`storage_location`** or **`storelocation`**: The storage location of the part. Can be a path similar to the category field. -**`amount`**, **`quantity`** or **`instock`**: The amount of the part in stock. If this value is not set, the part lot will be marked with "unknown amount" +**`storage_location`** or **`storelocation`**: The storage location of the part. Can be a path similar to the category +field. +**`amount`**, **`quantity`** or **`instock`**: The amount of the part in stock. If this value is not set, the part lot +will be marked with "unknown amount" -The following fields can be used to specify the supplier/distributor, supplier product number and the price of the part. This is only possible for a single supplier/distributor and price with this fields. If you need to specify multiple suppliers/distributors or prices, you have to use JSON format (or CSV) with nested objects. -**Please note that the supplier fields is required, if you want to import prices or supplier product numbers.**. If the supplier is not specified, the price and supplier product number fields will be ignored: +The following fields can be used to specify the supplier/distributor, supplier product number, and the price of the part. +This is only possible for a single supplier/distributor and price with these fields. If you need to specify multiple +suppliers/distributors or prices, you have to use JSON format (or CSV) with nested objects. +**Please note that the supplier fields is required, if you want to import prices or supplier product numbers**. If the +supplier is not specified, the price and supplier product number fields will be ignored: * **`supplier`**: The supplier of the part. Can be a path similar to the category field. * **`supplier_product_number`** or **`supplier_part_number`** or * **`spn`**: The supplier product number of the part. * **`price`**: The price of the part in the base currency of the database (by default euro). #### Example data -Here you can find some example data for the import of parts, you can use it as a template for your own import (especially the CSV file). + +Here you can find some example data for the import of parts, you can use it as a template for your own import ( +especially the CSV file). * [Part import CSV example]({% link assets/usage/import_export/part_import_example.csv %}) with all possible fields ## Export -By default every user, who can read the datastructure, can also export the data of this datastructure, as this does not give the user any additional information. +By default, every user, who can read the datastructure, can also export the data of this datastructure, as this does not +give the user any additional information. ### Exporting data structures (categories, manufacturers, etc.) -You can export data structures (like categories, manufacturers, etc.) in the respective edit page (e.g. Tools Panel -> Edit -> Category). -If you select a certain datastructure from your list, you can export it (and optionally all sub-datastructures) in the "Export" tab. -If you want to export all datastructures of a certain type (e.g. all categories in your database), you can select the "Export all" function in the "Import / Export" tab of the "new element" page. + +You can export data structures (like categories, manufacturers, etc.) in the respective edit page (e.g. Tools Panel -> +Edit -> Category). +If you select a certain data structure from your list, you can export it (and optionally all sub data structures) in the " +Export" tab. +If you want to export all data structures of a certain type (e.g. all categories in your database), you can select the " +Export all" function in the "Import / Export" tab of the "new element" page. You can select between the following export formats: -* **CSV** (Comma Separated Values): A semicolon separated list of values, where every line represents an element. This format can be imported into Excel or LibreOffice Calc and is easy to work with. However it does not support nested datastructures or sub data (like parameters, attachments, etc.), very well (many columns are generated, as every possible sub data is exported as a separate column). -* **JSON** (JavaScript Object Notation): A text-based format, which is easy to work with programming laguages. It supports nested datastructures and sub data (like parameters, attachments, etc.) very well. However it is not easy to work with in Excel or LibreOffice Calc and you maybe need to write some code to work with the exported data efficiently. -* **YAML** (Yet another Markup Language): Very similar to JSON -* **XML** (Extensible Markup Language): Good support with nested datastructures. Similar usecase as JSON and YAML. -Also you can select between the following export levels: +* **CSV** (Comma Separated Values): A semicolon-separated list of values, where every line represents an element. This + format can be imported into Excel or LibreOffice Calc and is easy to work with. However, it 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). +* **JSON** (JavaScript Object Notation): A text-based format, which is easy to work with programming languages. It + supports nested data structures and sub-data (like parameters, attachments, etc.) very well. However, it is not easy to + work with in Excel or LibreOffice Calc and you may need to write some code to work with the exported data + 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: + * **Simple**: This will only export very basic information about the name (like the name, or description for parts) -* **Extended**: This will export all commonly used information about this datastructure (like notes, options, etc) -* **Full**: This will export all available information about this datastructure (like all parameters, attachments) +* **Extended**: This will export all commonly used information about this data structure (like notes, options, etc.) +* **Full**: This will export all available information about this data structure (like all parameters, attachments) -Please note that the level will also be applied to all sub data or children elements. So if you select "Full" for a part, all the associated categories, manufacturers, footprints, etc. will also be exported with all available information, this can lead to very large export files. +Please note that the level will also be applied to all sub-data or children elements. So if you select "Full" for a +part, all the associated categories, manufacturers, footprints, etc. will also be exported with all available +information, this can lead to very large export files. ### Exporting parts -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 datastructures for more information about the export formats and levels. \ No newline at end of file +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. diff --git a/docs/usage/information_provider_system.md b/docs/usage/information_provider_system.md index eee66818..c6d4c83f 100644 --- a/docs/usage/information_provider_system.md +++ b/docs/usage/information_provider_system.md @@ -6,137 +6,276 @@ parent: Usage # Information provider system -Part-DB can create parts based on information from external sources: For example with the right setup you can just search for a part number -and Part-DB will query selected distributors and manufacturers for the part and create a part with the information it found. -This way your Part-DB parts automatically get datasheet links, prices, parameters and more, with just a few clicks. +Part-DB can create parts based on information from external sources: For example, with the right setup you can just +search for a part number +and Part-DB will query selected distributors and manufacturers for the part and create a part with the information it +found. +This way your Part-DB parts automatically get datasheet links, prices, parameters, and more, with just a few clicks. ## Usage -Before you can use the information provider system, you have to configure at least one information provider, which act as data source. +Before you can use the information provider system, you have to configure at least one information provider, which act +as data source. See below for a list of available information providers and available configuration options. -For many providers it is enough, to setup the API keys in the env configuration, some require an additional OAuth connection. -You can list all enabled information providers in the browser at `https://your-partdb-instance.tld/tools/info_providers/providers` (you need the right permission for it, see below). +For many providers it is enough, to set up the API keys in the env configuration, some require an additional OAuth +connection. +You can list all enabled information providers in the browser +at `https://your-partdb-instance.tld/tools/info_providers/providers` (you need the right permission for it, see below). -To use the information provider system, your user need to have the right permissions. Go to the permission management page of +To use the information provider system, your user need to have the right permissions. Go to the permission management +page of a user or a group and assign the permissions of the "Info providers" group in the "Miscellaneous" tab. -If you have the required permission you will find in the sidebar in the "Tools" section the entry "Create part from info provider". -Click this and you will land on a search page. Enter the part number you want to search for and select the information providers you want to use. +If you have the required permission you will find in the sidebar in the "Tools" section the entry "Create part from info +provider". +Click this and you will land on a search page. Enter the part number you want to search for and select the information +providers you want to use. -After you click Search, you will be presented with the results and can select the result that fits best. -With a click on the blue plus button, you will be redirected to the part creation page with the information already filled in. +After you click Search, you will be presented with the results and can select the result that fits best. +With a click on the blue plus button, you will be redirected to the part creation page with the information already +filled in. ![image]({% link assets/usage/information_provider_system/animation.gif %}) +If you want to update an existing part, go to the parts info page and click on the "Update from info provider" button in +the tools tab. You will be redirected to a search page, where you can search the info providers to automatically update this +part. + ## Alternative names -Part-DB tries to automatically find existing elements from your database for the information it got from the providers for fields like manufacturer, footprint, etc. -For this it searches for a element with the same name (case-insensitive) as the information it got from the provider. So e.g. if the provider returns "EXAMPLE CORP" as manufacturer, +Part-DB tries to automatically find existing elements from your database for the information it got from the providers +for fields like manufacturer, footprint, etc. +For this, it searches for an element with the same name (case-insensitive) as the information it got from the provider. So +e.g. if the provider returns "EXAMPLE CORP" as the manufacturer, Part-DB will automatically select the element with the name "Example Corp" from your database. -As the names of these fields differ from provider to provider (and maybe not even normalized for the same provider), you +As the names of these fields differ from provider to provider (and maybe not even normalized for the same provider), you can define multiple alternative names for an element (on their editing page). -For example if define a manufacturer "Example Corp" with the alternative names "Example Corp.", "Example Corp", "Example Corp. Inc." and "Example Corporation", +For example, if you define a manufacturer "Example Corp" with the alternative names "Example Corp.", "Example Corp", "Example +Corp. Inc." and "Example Corporation", then the provider can return any of these names and Part-DB will still automatically select the right element. -If Part-DB finds no matching element, it will automatically create a new one, when you do not change the value before saving. +If Part-DB finds no matching element, it will automatically create a new one, when you do not change the value before +saving. ## Attachment types The information provider system uses attachment types to differentiate between datasheets and image attachments. -For this it will create a "Datasheet" and "Image" attachment type on the first run. You can change the names of these +For this it will create a "Datasheet" and "Image" attachment type on the first run. You can change the names of these types in the attachment type settings (as long as you keep the "Datasheet"/"Image" in the alternative names field). -If you already have attachment types for images and datasheets and want the information provider system to use them, you can +If you already have attachment types for images and datasheets and want the information provider system to use them, you +can add the alternative names "Datasheet" and "Image" to the alternative names field of the attachment types. +## Bulk import + +If you want to update the information of multiple parts, you can use the bulk import system: Go to a part table and select +the parts you want to update. In the bulk actions dropdown select "Bulk info provider import" and click "Apply". +You will be redirected to a page, where you can select how part fields should be mapped to info provider fields, and the +results will be shown. + ## Data providers The system tries to be as flexible as possible, so many different information sources can be used. -Each information source is called am "info provider" and handles the communication with the external source. -The providers are just a driver which handles the communication with the different external sources and converts them into a common format Part-DB understands. +Each information source is called an "info provider" and handles the communication with the external source. +The providers are just a driver that handles the communication with the different external sources and converts them +into a common format Part-DB understands. That way it is pretty easy to create new providers as they just need to do very little work. -Normally the providers utilize an API of a service, and you need to create a account at the provider and get an API key. -Also there are limits on how many requests you can do per day or months, depending on the provider and your contract with them. +Normally the providers utilize an API of a service, and you need to create an account at the provider and get an API key. +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.) -### Ocotpart -The Octopart provider uses the [Octopart / Nexar API](https://nexar.com/api) to search for parts and getting informations. -To use it you have to create an account at Nexar and create a new application on the [Nexar Portal](https://portal.nexar.com/). -The name does not matter, but it is important that the application has access to the "Supply" scope. -In the Authorization tab, you will find the client ID and client secret, which you have to enter in the Part-DB env configuration (see below). +### Octopart -Please note that the Nexar API in the free plan is limited to 1000 results per month. -That means if you search for a keyword and results in 10 parts, then 10 will be substracted from your monthly limit. You can see your current usage on the Nexar portal. -Part-DB caches the search results internally, so if you have searched for a part before, it will not count against your monthly limit again, when you create it from the search results. +The Octopart provider uses the [Octopart / Nexar API](https://nexar.com/api) to search for parts and get information. +To use it you have to create an account at Nexar and create a new application on +the [Nexar Portal](https://portal.nexar.com/). +The name does not matter, but it is important that the application has access to the "Supply" scope. +In the Authorization tab, you will find the client ID and client secret, which you have to put in the Part-DB env +configuration (see below). -Following env configuration options are available: +Please note that the Nexar API in the free plan is limited to 1000 results per month. +That means if you search for a keyword and results in 10 parts, then 10 will be subtracted from your monthly limit. You +can see your current usage on the Nexar portal. +Part-DB caches the search results internally, so if you have searched for a part before, it will not count against your +monthly limit again, when you create it from the search results. + +The following env configuration options are available: * `PROVIDER_OCTOPART_CLIENT_ID`: The client ID you got from Nexar (mandatory) * `PROVIDER_OCTOPART_SECRET`: The client secret you got from Nexar (mandatory) -* `PROVIDER_OCTOPART_CURRENCY`: The currency you want to get prices in if available (optional, 3 letter ISO-code, default: `EUR`). If an offer is only available in a certain currency, -Part-DB will save the prices in their native currency, and you can use Part-DB currency conversion feature to convert it to your preferred currency. -* `PROVIDER_OCOTPART_COUNTRY`: The country you want to get prices in if available (optional, 2 letter ISO-code, default: `DE`). To get correct prices, you have to set this and the currency setting to the correct value. -* `PROVIDER_OCTOPART_SEARCH_LIMIT`: The maximum number of results to return per search (optional, default: `10`). This affects how quickly your monthly limit is used up. -* `PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS`: If set to `true`, only offers from [authorized sellers](https://octopart.com/authorized) will be returned (optional, default: `false`). +* `PROVIDER_OCTOPART_CURRENCY`: The currency you want to get prices in if available (optional, 3 letter ISO-code, + default: `EUR`). If an offer is only available in a certain currency, + Part-DB will save the prices in their native currency, and you can use Part-DB currency conversion feature to convert + it to your preferred currency. +* `PROVIDER_OCTOPART_COUNTRY`: The country you want to get prices in if available (optional, 2 letter ISO-code, + default: `DE`). To get the correct prices, you have to set this and the currency setting to the correct value. +* `PROVIDER_OCTOPART_SEARCH_LIMIT`: The maximum number of results to return per search (optional, default: `10`). This + affects how quickly your monthly limit is used up. +* `PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS`: If set to `true`, only offers + from [authorized sellers](https://octopart.com/authorized) will be returned (optional, default: `false`). -**Attention**: If you change the octopart clientID after you have already used the provider, you have to remove the OAuth token in the Part-DB database. Remove the entry in the table `oauth_tokens` with the name `ip_octopart_oauth`. +**Attention**: If you change the Octopart clientID after you have already used the provider, you have to remove the +OAuth token in the Part-DB database. Remove the entry in the table `oauth_tokens` with the name `ip_octopart_oauth`. ### Digi-Key -The Digi-Key provider uses the [Digi-Key API](https://developer.digikey.com/) to search for parts and getting shopping information from [Digi-Key](https://www.digikey.com/). -To use it you have to create an account at Digi-Key and get an API key on the [Digi-Key API page](https://developer.digikey.com/). -You must create an organization there and create a "Production app". Most settings are not important, you just have to grant access to the "Product Information" API. -You will get an Client ID and a Client Secret, which you have to enter in the Part-DB env configuration (see below). -Following env configuration options are available: +The Digi-Key provider uses the [Digi-Key API](https://developer.digikey.com/) to search for parts and get shopping +information from [Digi-Key](https://www.digikey.com/). +To use it you have to create an account at Digi-Key and get an API key on +the [Digi-Key API page](https://developer.digikey.com/). +You must create an organization there and create a "Production app". Most settings are not important, you just have to +grant access to the "Product Information" API. +You will get a Client ID and a Client Secret, which you have to put in the Part-DB env configuration (see below). + +The following env configuration options are available: + * `PROVIDER_DIGIKEY_CLIENT_ID`: The client ID you got from Digi-Key (mandatory) -* `PROVIDER_DIGIKEY_CLIENT_SECRET`: The client secret you got from Digi-Key (mandatory) +* `PROVIDER_DIGIKEY_SECRET`: The client secret you got from Digi-Key (mandatory) * `PROVIDER_DIGIKEY_CURRENCY`: The currency you want to get prices in (optional, default: `EUR`) * `PROVIDER_DIGIKEY_LANGUAGE`: The language you want to get the descriptions in (optional, default: `en`) * `PROVIDER_DIGIKEY_COUNTRY`: The country you want to get the prices for (optional, default: `DE`) -The Digi-Key provider needs an additional OAuth connection. To do this, go to the information provider list (`https://your-partdb-instance.tld/tools/info_providers/providers`), -go the Digi-Key provider (in the disabled page) and click on the "Connect OAuth" button. You will be redirected to Digi-Key, where you have to login and grant access to the app. +The Digi-Key provider needs an additional OAuth connection. To do this, go to the information provider +list (`https://your-partdb-instance.tld/tools/info_providers/providers`), +go to Digi-Key provider (in the disabled page), and click on the "Connect OAuth" button. You will be redirected to +Digi-Key, where you have to log in and grant access to the app. To do this your user needs the "Manage OAuth tokens" permission from the "System" section in the "System" tab. -The OAuth connection should only be needed once, but if you have any problems with the provider, just click the button again, to establish a new connection. +The OAuth connection should only be needed once, but if you have any problems with the provider, just click the button +again, to establish a new connection. ### TME -The TME provider use the API of [TME](https://www.tme.eu/) to search for parts and getting shopping information from them. -To use it you have to create an account at TME and get an API key on the [TME API page](https://developers.tme.eu/en/). -You have to generate a new anonymous key there and enter the key and secret in the Part-DB env configuration (see below). -Following env configuration options are available: -* `PROVIDER_TME_API_KEY`: The API key you got from TME (mandatory) -* `PROVIDER_TME_API_SECRET`: The API secret you got from TME (mandatory) +The TME provider uses the API of [TME](https://www.tme.eu/) to search for parts and get shopping information from +them. +To use it you have to create an account at TME and get an API key on the [TME API page](https://developers.tme.eu/en/). +You have to generate a new anonymous key there and enter the key and secret in the Part-DB env configuration (see +below). + +The following env configuration options are available: + +* `PROVIDER_TME_KEY`: The API key you got from TME (mandatory) +* `PROVIDER_TME_SECRET`: The API secret you got from TME (mandatory) * `PROVIDER_TME_CURRENCY`: The currency you want to get prices in (optional, default: `EUR`) -* `PROVIDER_TME_LANGUAGE`: The language you want to get the descriptions in (`en`, `de` and `pl`) (optional, default: `en`) +* `PROVIDER_TME_LANGUAGE`: The language you want to get the descriptions in (`en`, `de` and `pl`) (optional, + default: `en`) * `PROVIDER_TME_COUNTRY`: The country you want to get the prices for (optional, default: `DE`) -* `PROVIDER_TME_GET_GROSS_PRICES`: If this is set to `1` the prices will be gross prices (including tax), otherwise net prices (optional, default: `0`) +* `PROVIDER_TME_GET_GROSS_PRICES`: If this is set to `1` the prices will be gross prices (including tax), otherwise net + prices (optional, default: `0`) ### Farnell / Element14 / Newark -The Farnell provider uses the [Farnell API](https://partner.element14.com/) to search for parts and getting shopping information from [Farnell](https://www.farnell.com/). -You have to create an account at Farnell and get an API key on the [Farnell API page](https://partner.element14.com/). -Register a new application there (settings does not matter, as long as you select the "Product Search API") and you will get an API key. + +The Farnell provider uses the [Farnell API](https://partner.element14.com/) to search for parts and get shopping +information from [Farnell](https://www.farnell.com/). +You have to create an account at Farnell and get an API key on the [Farnell API page](https://partner.element14.com/). +Register a new application there (settings do not matter, as long as you select the "Product Search API") and you will +get an API key. + +The following env configuration options are available: + +* `PROVIDER_ELEMENT14_KEY`: The API key you got from Farnell (mandatory) +* `PROVIDER_ELEMENT14_STORE_ID`: The store ID you want to use. This decides the language of results, currency and + country of prices (optional, default: `de.farnell.com`, + see [here](https://partner.element14.com/docs/Product_Search_API_REST__Description) for available values) + +### Mouser + +The Mouser provider uses the [Mouser API](https://www.mouser.de/api-home/) to search for parts and get shopping +information from [Mouser](https://www.mouser.com/). +You have to create an account at Mouser and register for an API key for the Search API on +the [Mouser API page](https://www.mouser.de/api-home/). +You will receive an API token, which you have to put in the Part-DB env configuration (see below): +At the registration you choose a country, language, and currency in which you want to get the results. Following env configuration options are available: -* `PROVIDER_ELEMENT14_KEY`: The API key you got from Farnell (mandatory) -* `PROVIDER_ELEMENT14_STORE_ID`: The store ID you want to use. This decides the language of results, currency and country of prices (optional, default: `de.farnell.com`, see [here](https://partner.element14.com/docs/Product_Search_API_REST__Description) for availailable values) +* `PROVIDER_MOUSER_KEY`: The API key you got from Mouser (mandatory) +* `PROVIDER_MOUSER_SEARCH_LIMIT`: The maximum number of results to return per search (maximum 50) +* `PROVIDER_MOUSER_SEARCH_OPTION`: You can choose an option here to restrict the search results to RoHs compliant and + available parts. Possible values are `None`, `Rohs`, `InStock`, `RohsAndInStock`. +* `PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE`: A bit of an obscure option. The original description of Mouser is: Used + when searching for keywords in the language specified when you signed up for Search API. + +### LCSC + +[LCSC](https://www.lcsc.com/) is a Chinese distributor of electronic parts. It does not offer a public API, but the LCSC +webshop uses an internal JSON based API to render the page. Part-DB can use this inofficial API to get part information +from LCSC. + +**Please note that the use of this internal API is not intended or endorsed by LCSC and it could break at any time. So use it at your own risk.** + +An API key is not required, it is enough to enable the provider using the following env configuration options: + +* `PROVIDER_LCSC_ENABLED`: Set this to `1` to enable the LCSC provider +* `PROVIDER_LCSC_CURRENCY`: The currency you want to get prices in (see LCSC webshop for available currencies, default: `EUR`) + +### OEMsecrets + +The oemsecrets provider uses the [oemsecrets API](https://www.oemsecrets.com/) to search for parts and get shopping +information from them. Similar to octopart it aggregates offers from different distributors. + +You can apply for a free API key on the [oemsecrets API page](https://www.oemsecrets.com/api/) and put the key you get +in the Part-DB env configuration (see below). + +The following env configuration options are available: + +* `PROVIDER_OEMSECRETS_KEY`: The API key you got from oemsecrets (mandatory) +* `PROVIDER_OEMSECRETS_COUNTRY_CODE`: The two-letter code of the country you want to get the prices for +* `PROVIDER_OEMSECRETS_CURRENCY`: The currency you want to get prices in (optional, default: `EUR`) +* `PROVIDER_OEMSECRETS_ZERO_PRICE`: If set to `1`, parts with a price of 0 will be included in the search results, otherwise + they will be excluded (optional, default: `0`) +* `PROVIDER_OEMSECRETS_SET_PARAM`: If set to `1`, the provider will try to extract parameters from the part description +* `PROVIDER_OEMSECRETS_SORT_CRITERIA`: The criteria to sort the search results by. If set to 'C', it further sorts by +completeness (prioritizing items with the most detailed information). If set to 'M', it further sorts by manufacturer name. +If set to any other value, no sorting is performed. + +### 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 as it is a valid Symfony service, it will be automatically loaded and can be used. -Besides some metadata functions, you have to implement the `searchByKeyword()` and `getDetails()` functions, which do the actual API requests and return the information to Part-DB. + +To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long +as it is a valid Symfony service, it will be automatically loaded and can be used. +Besides some metadata functions, you have to implement the `searchByKeyword()` and `getDetails()` functions, which do +the actual API requests and return the information to Part-DB. See the existing providers for examples. If you created a new provider, feel free to create a pull request to add it to the Part-DB core. ## Result caching + To reduce the number of API calls against the providers, the results are cached: + * The search results (exact search term) are cached for 7 days * The product details are cached for 4 days -If you need a fresh result, you can clear the cache by running `php .\bin\console cache:pool:clear info_provider.cache` on the command line. +If you need a fresh result, you can clear the cache by running `php .\bin\console cache:pool:clear info_provider.cache` +on the command line. The default `php bin/console cache:clear` also clears the result cache, as it clears all caches. diff --git a/docs/usage/keybindings.md b/docs/usage/keybindings.md index f3224b89..771d7684 100644 --- a/docs/usage/keybindings.md +++ b/docs/usage/keybindings.md @@ -9,104 +9,107 @@ parent: Usage This page lists all the keybindings of Part-DB. Currently, there are only the special character keybindings. ## Special characters -Using the keybindings below (Alt + key) you can insert special characters into the text fields of Part-DB. This works on all text and search fields in Part-DB. + +Using the keybindings below (Alt + key) you can insert special characters into the text fields of Part-DB. This works on +all text and search fields in Part-DB. ### Greek letters -| Key | Character | -|---------------------|---------------------| -| **Alt + a** | α (Alpha) | -| **Alt + Shift + A** | Α (Alpha uppercase) | -| **Alt + b** | β (Beta) | -| **Alt + Shift + B** | Β (Beta uppercase) | -| **Alt + g** | γ (Gamma) | -| **Alt + Shift + G** | Γ (Gamma uppercase) | -| **Alt + d** | δ (Delta) | -| **Alt + Shift + D** | Δ (Delta uppercase) | -| **Alt + e** | ε (Epsilon) | +| Key | Character | +|---------------------|-----------------------| +| **Alt + a** | α (Alpha) | +| **Alt + Shift + A** | Α (Alpha uppercase) | +| **Alt + b** | β (Beta) | +| **Alt + Shift + B** | Β (Beta uppercase) | +| **Alt + g** | γ (Gamma) | +| **Alt + Shift + G** | Γ (Gamma uppercase) | +| **Alt + d** | δ (Delta) | +| **Alt + Shift + D** | Δ (Delta uppercase) | +| **Alt + e** | ε (Epsilon) | | **Alt + Shift + E** | Ε (Epsilon uppercase) | -| **Alt + z** | ζ (Zeta) | -| **Alt + Shift + Z** | Ζ (Zeta uppercase) | -| **Alt + h** | η (Eta) | -| **Alt + Shift + H** | Η (Eta uppercase) | -| **Alt + q** | θ (Theta) | -| **Alt + Shift + Q** | Θ (Theta uppercase) | -| **Alt + i** | ι (Iota) | -| **Alt + Shift + I** | Ι (Iota uppercase) | -| **Alt + k** | κ (Kappa) | -| **Alt + Shift + K** | Κ (Kappa uppercase) | -| **Alt + l** | λ (Lambda) | -| **Alt + Shift + L** | Λ (Lambda uppercase) | -| **Alt + m** | μ (Mu) | -| **Alt + Shift + M** | Μ (Mu uppercase) | -| **Alt + n** | ν (Nu) | -| **Alt + Shift + N** | Ν (Nu uppercase) | -| **Alt + x** | ξ (Xi) | -| **Alt + Shift + x** | Ξ (Xi uppercase) | -| **Alt + o** | ο (Omicron) | +| **Alt + z** | ζ (Zeta) | +| **Alt + Shift + Z** | Ζ (Zeta uppercase) | +| **Alt + h** | η (Eta) | +| **Alt + Shift + H** | Η (Eta uppercase) | +| **Alt + q** | θ (Theta) | +| **Alt + Shift + Q** | Θ (Theta uppercase) | +| **Alt + i** | ι (Iota) | +| **Alt + Shift + I** | Ι (Iota uppercase) | +| **Alt + k** | κ (Kappa) | +| **Alt + Shift + K** | Κ (Kappa uppercase) | +| **Alt + l** | λ (Lambda) | +| **Alt + Shift + L** | Λ (Lambda uppercase) | +| **Alt + m** | μ (Mu) | +| **Alt + Shift + M** | Μ (Mu uppercase) | +| **Alt + n** | ν (Nu) | +| **Alt + Shift + N** | Ν (Nu uppercase) | +| **Alt + x** | ξ (Xi) | +| **Alt + Shift + x** | Ξ (Xi uppercase) | +| **Alt + o** | ο (Omicron) | | **Alt + Shift + O** | Ο (Omicron uppercase) | -| **Alt + p** | π (Pi) | -| **Alt + Shift + P** | Π (Pi uppercase) | -| **Alt + r** | ρ (Rho) | -| **Alt + Shift + R** | Ρ (Rho uppercase) | -| **Alt + s** | σ (Sigma) | -| **Alt + Shift + S** | Σ (Sigma uppercase) | -| **Alt + t** | τ (Tau) | -| **Alt + Shift + T** | Τ (Tau uppercase) | -| **Alt + u** | υ (Upsilon) | +| **Alt + p** | π (Pi) | +| **Alt + Shift + P** | Π (Pi uppercase) | +| **Alt + r** | ρ (Rho) | +| **Alt + Shift + R** | Ρ (Rho uppercase) | +| **Alt + s** | σ (Sigma) | +| **Alt + Shift + S** | Σ (Sigma uppercase) | +| **Alt + t** | τ (Tau) | +| **Alt + Shift + T** | Τ (Tau uppercase) | +| **Alt + u** | υ (Upsilon) | | **Alt + Shift + U** | Υ (Upsilon uppercase) | -| **Alt + f** | φ (Phi) | -| **Alt + Shift + F** | Φ (Phi uppercase) | -| **Alt + y** | ψ (Psi) | -| **Alt + Shift + Y** | Ψ (Psi uppercase) | -| **Alt + c** | χ (Chi) | -| **Alt + Shift + C** | Χ (Chi uppercase) | -| **Alt + w** | ω (Omega) | -| **Alt + Shift + W** | Ω (Omega uppercase) | +| **Alt + f** | φ (Phi) | +| **Alt + Shift + F** | Φ (Phi uppercase) | +| **Alt + y** | ψ (Psi) | +| **Alt + Shift + Y** | Ψ (Psi uppercase) | +| **Alt + c** | χ (Chi) | +| **Alt + Shift + C** | Χ (Chi uppercase) | +| **Alt + w** | ω (Omega) | +| **Alt + Shift + W** | Ω (Omega uppercase) | ### Mathematical symbols -| Key | Character | -|----------------------|-------------------------------------------| -| **Alt + 1** | ∑ (Sum symbol) | -| **Alt + Shift + 1** | ∏ (Product symbol) | -| **Alt + 2** | ∫ (Integral symbol) | -| **Alt + Shift + 2** | ∂ (Partial derivation) | -| **Alt + 3** | ≤ (Less or equal symbol) | -| **Alt + Shift + 3** | ≥ (Greater or equal symbol) | -| **Alt + 4** | ∞ (Infinity symbol) | -| **Alt + Shift + 4** | ∅ (Empty set symbol) | -| **Alt + 5** | ≈ (Approximatley) | -| **Alt + Shift + 5** | ≠ (Not equal symbol) | -| **Alt + 6** | ∈ (Element of) | -| **Alt + Shift + 6** | ∉ (Not element of) | -| **Alt + 7** | ∨ (Logical or) | -| **Alt + Shift + 7** | ∧ (Logical and) | -| **Alt + 8** | ∠ (Angle symbol) | -| **Alt + Shift + 8** | ∝ (Proportional to) | -| **Alt + 9** | √ (Square root) | -| **Alt + Shift + 9** | ∛ (Cube root) | -| **Alt + 0** | ± (Plus minus) | -| **Alt + Shift + 0** | ∓ (Minus plus) | +| Key | Character | +|---------------------|-----------------------------| +| **Alt + 1** | ∑ (Sum symbol) | +| **Alt + Shift + 1** | ∏ (Product symbol) | +| **Alt + 2** | ∫ (Integral symbol) | +| **Alt + Shift + 2** | ∂ (Partial derivation) | +| **Alt + 3** | ≤ (Less or equal symbol) | +| **Alt + Shift + 3** | ≥ (Greater or equal symbol) | +| **Alt + 4** | ∞ (Infinity symbol) | +| **Alt + Shift + 4** | ∅ (Empty set symbol) | +| **Alt + 5** | ≈ (Approximately) | +| **Alt + Shift + 5** | ≠ (Not equal symbol) | +| **Alt + 6** | ∈ (Element of) | +| **Alt + Shift + 6** | ∉ (Not element of) | +| **Alt + 7** | ∨ (Logical or) | +| **Alt + Shift + 7** | ∧ (Logical and) | +| **Alt + 8** | ∠ (Angle symbol) | +| **Alt + Shift + 8** | ∝ (Proportional to) | +| **Alt + 9** | √ (Square root) | +| **Alt + Shift + 9** | ∛ (Cube root) | +| **Alt + 0** | ± (Plus minus) | +| **Alt + Shift + 0** | ∓ (Minus plus) | ### Currency symbols -Please not the following keybindings are bound to a specific keycode. The key character is not the same on all keyboards. +Please note, the following keybindings are bound to a specific keycode. The key character is not the same on all +keyboards. It is given here for a US keyboard layout. For a German keyboard layout, replace ; with ö, and ' with ä. -| Key | Character | -|---------------------------------|---------------------------| -| **Alt + ;** (code 192) | € (Euro currency symbol) | -| **Alt + Shift + ;** (code 192) | £ (Pound currency symbol) | -| **Alt + '** (code 222) | ¥ (Yen currency symbol) | +| Key | Character | +|---------------------------------|----------------------------| +| **Alt + ;** (code 192) | € (Euro currency symbol) | +| **Alt + Shift + ;** (code 192) | £ (Pound currency symbol) | +| **Alt + '** (code 222) | ¥ (Yen currency symbol) | | **Alt + Shift + '** (code 222) | $ (Dollar currency symbol) | - ### Others -Please not the following keybindings are bound to a specific keycode. The key character is not the same on all keyboards. +Please note the following keybindings are bound to a specific keycode. The key character is not the same on all +keyboards. It is given here for a US keyboard layout. For a German keyboard layout, replace `[` with `0`, and `]` with `´`. @@ -114,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/labels.md b/docs/usage/labels.md index 35c6d317..4c3f8b32 100644 --- a/docs/usage/labels.md +++ b/docs/usage/labels.md @@ -6,103 +6,268 @@ parent: Usage # Labels -Part-DB support the generation and printing of labels for parts, part lots and storelocation. -You can use the "Tools -> Labelgenerator" menu entry to create labels, or click the label generation link on the part. +Part-DB supports the generation and printing of labels for parts, part lots and storage locations. +You can use the "Tools -> Label generator" menu entry to create labels or click the label generation link on the part. -You can define label templates by creating Label profiles. This way you can create many similar looking labels with for +You can define label templates by creating label profiles. This way you can create many similar-looking labels for many parts. -The content of the labels is defined by the templates content field. You can use the WYSIWYG editor to create and style the content (or write HTML code). +The content of the labels is defined by the template's content field. You can use the WYSIWYG editor to create and style +the content (or write HTML code). Using the "Label placeholder" menu in the editor, you can insert placeholders for the data of the parts. It will be replaced by the concrete data when the label is generated. - + ## Label placeholders + A placeholder has the format `[[PLACEHOLDER]]` and will be filled with the concrete data by Part-DB. -You can use the "Placeholders" dropdown in content editor, to automatically insert the placeholders. +You can use the "Placeholders" dropdown in the content editor, to automatically insert the placeholders. ### Common -| Placeholder | Description | Example | -|---|---|---| -| `[[USERNAME]]` | The user name of the currently logged in user | admin | -| `[[USERNAME_FULL]]` | The full name of the current user | John Doe (@admin) | -| `[[DATETIME]]` | The current date and time in the selected locale | 31.12.2017, 18:34:11 | -| `[[DATE]]` | The current date in the selected locale | 31.12.2017 | -| `[[TIME]]` | The current time in the selected locale | 18:34:11 | -| `[[INSTALL_NAME]]` | The name of the current installation (see $config['partdb_title']) | Part-DB | -| `[[INSTANCE_URL]]` | The URL of the current installation | https://demo.part-db.de | +| Placeholder | Description | Example | +|---------------------|--------------------------------------------------------------------|-------------------------| +| `[[USERNAME]]` | The user name of the currently logged in user | admin | +| `[[USERNAME_FULL]]` | The full name of the current user | John Doe (@admin) | +| `[[DATETIME]]` | The current date and time in the selected locale | 31.12.2017, 18:34:11 | +| `[[DATE]]` | The current date in the selected locale | 31.12.2017 | +| `[[TIME]]` | The current time in the selected locale | 18:34:11 | +| `[[INSTALL_NAME]]` | The name of the current installation (see $config['partdb_title']) | Part-DB | +| `[[INSTANCE_URL]]` | The URL of the current installation | https://demo.part-db.de | ### Parts -| Placeholder | Description | Example | -|---|---|---| -| `[[ID]]` | The internal ID of the part | 24 | -| `[[NAME]]` | The name of the part | ATMega328 | -| `[[CATEGORY]]` | The name of the category (without path) | AVRs | -| `[[CATEGORY_FULL]]` | The full path of the category | Aktiv->MCUs->AVRs | -| `[[MANUFACTURER]]` | The name of the manufacturer | Atmel | -| `[[MANUFACTURER_FULL]]` | The full path of the manufacturer | Halbleiterhersteller->Atmel | -| `[[FOOTPRINT]]` | The name of the footprint (without path) | DIP-32 | -| `[[FOOTPRINT_FULL]]` | The full path of the footprint | Bedrahtet->DIP->DIP-32 | -| `[[MASS]]` | The mass of the part | 123.4 g | -| `[[MPN]]` | The manufacturer product number | BC547ACT | -| `[[TAGS]]` | The tags of the part | SMD, Tag1 | -| `[[M_STATUS]]` | The manufacturing status of the part | Active | -| `[[DESCRIPTION]]` | The rich text description of the part | *NPN* | -| `[[DESCRIPTION_T]]` | The description as plain text | NPN | -| `[[COMMENT]]` | The rich text comment of the part | | -| `[[COMMENT_T]]` | The comment as plain text | | -| `[[LAST_MODIFIED]]` | The datetime when the element was last modified | 2/26/16, 5:38 PM | -| `[[CREATION_DATE]]` | The datetime when the element was created | 2/26/16, 5:38 PM | +| Placeholder | Description | Example | +|-------------------------|-------------------------------------------------|-----------------------------| +| `[[ID]]` | The internal ID of the part | 24 | +| `[[NAME]]` | The name of the part | ATMega328 | +| `[[CATEGORY]]` | The name of the category (without path) | AVRs | +| `[[CATEGORY_FULL]]` | The full path of the category | Aktiv->MCUs->AVRs | +| `[[MANUFACTURER]]` | The name of the manufacturer | Atmel | +| `[[MANUFACTURER_FULL]]` | The full path of the manufacturer | Halbleiterhersteller->Atmel | +| `[[FOOTPRINT]]` | The name of the footprint (without path) | DIP-32 | +| `[[FOOTPRINT_FULL]]` | The full path of the footprint | Bedrahtet->DIP->DIP-32 | +| `[[MASS]]` | The mass of the part | 123.4 g | +| `[[MPN]]` | The manufacturer product number | BC547ACT | +| `[[TAGS]]` | The tags of the part | SMD, Tag1 | +| `[[M_STATUS]]` | The manufacturing status of the part | Active | +| `[[DESCRIPTION]]` | The rich text description of the part | *NPN* | +| `[[DESCRIPTION_T]]` | The description as plain text | NPN | +| `[[COMMENT]]` | The rich text comment of the part | | +| `[[COMMENT_T]]` | The comment as plain text | | +| `[[LAST_MODIFIED]]` | The datetime when the element was last modified | 2/26/16, 5:38 PM | +| `[[CREATION_DATE]]` | The datetime when the element was created | 2/26/16, 5:38 PM | ### Part lot -| Placeholder | Description | Example | -|---|---|---| -| `[[LOT_ID]]` | Part lot ID | 123 | -| `[[LOT_NAME]]` | Part lot name | | -| `[[LOT_COMMENT]]` | Part lot comment | | -| `[[EXPIRATION_DATE]]` | Expiration date of the part lot | | -| `[[AMOUNT]]` | The amount of parts in this lot | 12 | -| `[[LOCATION]]` | The storage location of this part lot | Location A | -| `[[LOCATION_FULL]]` | The full path of the storage location | Location -> Location A | +| Placeholder | Description | Example | +|-----------------------|---------------------------------------|------------------------| +| `[[LOT_ID]]` | Part lot ID | 123 | +| `[[LOT_NAME]]` | Part lot name | | +| `[[LOT_COMMENT]]` | Part lot comment | | +| `[[EXPIRATION_DATE]]` | Expiration date of the part lot | | +| `[[AMOUNT]]` | The amount of parts in this lot | 12 | +| `[[LOCATION]]` | The storage location of this part lot | Location A | +| `[[LOCATION_FULL]]` | The full path of the storage location | Location -> Location A | ### Storelocation -| Placeholder | Description | Example | -|---|---|---| -| `[[ID]]` | ID of the storage location | | -| `[[NAME]]` | Name of the storage location | Location A | -| `[[FULL_PATH]]` | The full path of the storage location | Location -> Location A | -| `[[PARENT]]` | The name of the parent location | Location | -| `[[PARENT_FULL_PATH]]` | The full path of the storage location | | -| `[[COMMENT]]` | The comment of the storage location | | -| `[[COMMENT_T]]` | The plain text version of the comment | -| `[[LAST_MODIFIED]]` | The datetime when the element was last modified | 2/26/16, 5:38 PM | -| `[[CREATION_DATE]]` | The datetime when the element was created | 2/26/16, 5:38 PM | +| Placeholder | Description | Example | +|------------------------|-------------------------------------------------|------------------------| +| `[[ID]]` | ID of the storage location | | +| `[[NAME]]` | Name of the storage location | Location A | +| `[[FULL_PATH]]` | The full path of the storage location | Location -> Location A | +| `[[PARENT]]` | The name of the parent location | Location | +| `[[PARENT_FULL_PATH]]` | The full path of the storage location | | +| `[[COMMENT]]` | The comment of the storage location | | +| `[[COMMENT_T]]` | The plain text version of the comment | +| `[[LAST_MODIFIED]]` | The datetime when the element was last modified | 2/26/16, 5:38 PM | +| `[[CREATION_DATE]]` | The datetime when the element was created | 2/26/16, 5:38 PM | ## Twig mode -If you select "Twig" in parser mode under advanced settings, you can input a twig template in the lines field (activate source mode). You can use most of the twig tags and filters listed in [offical documentation](https://twig.symfony.com/doc/3.x/). +If you select "Twig" in parser mode under advanced settings, you can input a twig template in the lines field (activate +source mode). You can use most of the twig tags and filters listed +in [official documentation](https://twig.symfony.com/doc/3.x/). -The following variables are in injected into Twig and can be accessed using `{% raw %}{{ variable }}` (or `{% raw %}{{ variable.property }}{% endraw %}`): +Twig allows you for much more complex and dynamic label generation. You can use loops, conditions, and functions to create +the label content and you can access almost all data Part-DB offers. The label templates are evaluated in a special sandboxed environment, +where only certain operations are allowed. Only read access to entities is allowed. However as it circumvents Part-DB normal permission system, +the twig mode is only available to users with the "Twig mode" permission. -| Variable name | Description | -|--------------------------------------| ----------- | -| `{% raw %}{{ element }}{% endraw %}` | The target element, selected in label dialog | -| `{% raw %}{{ user }}{% endraw %}` | The current logged in user. Null if you are not logged in | -| `{% raw %}{{ install_title }}{% endraw %}` | The name of the current Part-DB instance (similar to [[INSTALL_NAME]] placeholder). | -| `{% raw %}{{ page }}{% endraw %}` | The page number (the nth-element for which the label is generated | +The following variables are in injected into Twig and can be accessed using `{% raw %}{{ variable }}{% endraw %}` ( +or `{% raw %}{{ variable.property }}{% endraw %}`): +| Variable name | Description | +|--------------------------------------------|--------------------------------------------------------------------------------------| +| `{% raw %}{{ element }}{% endraw %}` | The target element, selected in label dialog. | +| `{% raw %}{{ user }}{% endraw %}` | The current logged in user. Null if you are not logged in | +| `{% raw %}{{ install_title }}{% endraw %}` | The name of the current Part-DB instance (similar to [[INSTALL_NAME]] placeholder). | +| `{% raw %}{{ page }}{% endraw %}` | The page number (the nth-element for which the label is generated | +| `{% raw %}{{ last_page }}{% endraw %}` | The page number of the last element. Equals the number of all pages / element labels | +| `{% raw %}{{ paper_width }}{% endraw %}` | The width of the label paper in mm | +| `{% raw %}{{ paper_height }}{% endraw %}` | The height of the label paper in mm | + +### Use the placeholders in twig mode + +You can use the placeholders described above in the twig mode on `element` using the `{% raw %}{{ placeholder('PLACEHOLDER', element) }}{% endraw %}` +function or the ``{{ "[[PLACEHOLDER]]"|placeholders(element) }}`` filter: + +```twig +{% raw %} +{# The function can be used to get the a single placeholder value of an element, if the placeholder does not exist, null is returned #} +{{ placeholder('[[NAME]]', element) }} + +{# The filter can be used to replace all placeholders in a string with the values of the element #} +{{ "[[NAME]]: [[DESCRIPTION]]"|placeholders(element) }} + +{# Using the apply environment every placeholder in the apply block will be replaced automatically #} +{% apply placeholders(element) %} + [[NAME]]: [[DESCRIPTION]] +{% endapply %} + +{# If the block contains HTML use placeholders(element)|raw to prevent escaping of the HTML #} +{% apply placeholders(element)|raw %} + [[NAME]]: [[DESCRIPTION]] +{% endapply %} + +{% endraw %} +``` + +### Important entity fields in twig mode + +In twig mode you have access to many fields of the entity you are generating the label for and their associated entities. +Following are some important fields of the entities listed. See the [SandboxedTwigFactory service](https://github.com/Part-DB/Part-DB-server/blob/master/src/Services/LabelSystem/SandboxedTwigFactory.php) for the full list of allowed class methods. + +Please not that the field names might change in the future. + +#### Part + +| Field name | Description | +|---------------------|-----------------------------------------------------------------------------------------------| +| `id` | The internal ID of the part | +| `name` | The name of the part | +| `category` | The category of the part | +| `manufacturer` | The manufacturer of the part | +| `footprint` | The footprint of the part | +| `mass` | The mass of the part | +| `ManufacturerProductNumber` | The manufacturer product number of the part | +| `tags` | The tags of the part | +| `description` | The rich text (markdown) description of the part | +| `comment` | The rich text (markdown) comment of the part | +| `lastModified` | The datetime object when the part was last modified | +| `creationDate` | The datetime object when the part was created | +| `ipn` | The internal part number of the part | +| `partUnit` | The unit of the part | +| `amountSum` | The sum of the amount of all part lots of this part | +| `amountUnknwon` | Bool: True if there is at least one part lot with unknown amount | +| `partLots` | The part lots of the part | +| `parameters` | The parameters of the part | +| `orderdetails` | The order details of the part as array of Orderdetails | + +#### Part lot + +| Field name | Description | +|---------------------|-----------------------------------------------------------------------------------------------| +| `id` | The internal ID of the part lot | +| `name` | The name of the part lot | +| `comment` | The rich text (markdown) comment of the part lot | +| `expirationDate` | The expiration date of the part lot (as Datetime object) | +| `amount` | The amount of parts in this lot | +| `storageLocation` | The storage location of this part lot | +| `part` | The part of this part lot | +| `needsRefill` | Bool: True if the part lot needs a refill | +| `expired` | Bool: True if the part lot is expired | +| `vendorBarcode` | The vendor barcode field of the lot | + +#### Structural entities like categories, manufacturers, footprints, and storage locations + +| Field name | Description | +|---------------------|-----------------------------------------------------------------------------------------------| +| `id` | The internal ID of the entity | +| `name` | The name of the entity | +| `comment` | The rich text (markdown) comment of the entity | +| `parent` | The parent entity of the entity | +| `children` | The children entities of the entity | +| `lastModified` | The datetime object when the entity was last modified | +| `creationDate` | The datetime object when the entity was created | +| `level` | The level of the entity in the hierarchy | +| `fullPath` | The full path of the entity (you can pass the delimiter as parameter) | +| `pathArray` | The path of the entity as array of strings | + +#### Orderdetails + +| Field name | Description | +|---------------------|-----------------------------------------------------------------------------------------------| +| `id` | The internal ID of the order detail | +| `part` | The part of the order detail | +| `supplier` | The supplier/distributor of the order detail | +| `obsolete` | Bool: True if the order detail is obsolete | +| `pricedetails` | The price details of the order detail as array of Pricedetails | + +#### Pricedetails + +| Field name | Description | +|---------------------|-----------------------------------------------------------------------------------------------| +| `id` | The internal ID of the price detail | +| `price` | The price of the price detail | +| `currency` | The currency of the price detail | +| `currencyIsoCode` | The ISO code of the used currency | +| `pricePerUnit` | The price per unit of the price detail | +| `priceRelatedQuantity` | The related quantity of the price detail | +| `minDiscountQuantity` | The minimum discount quantity of the price detail | + +#### User + +| Field name | Description | +|---------------------|-----------------------------------------------------------------------------------------------| +| `id` | The internal ID of the user | +| `username` | The username of the user | +| `email` | The email of the user | +| `fullName` | The full name of the user | +| `lastName` | The last name of the user | +| `firstName` | The first name of the user | +| `department` | The department of the user | + + +### Part-DB specific twig functions and filters + +Part-DB offers some custom twig functions and filters, which can be used in the twig mode and ease the rendering of +certain data: + +#### Functions + +| Function name | Description | +|----------------------------------------------|-----------------------------------------------------------------------------------------------| +| `placeholder(placeholder, element)` | Get the value of a placeholder of an element | +| `entity_type(element)` | Get the type of an entity as string | +| `entity_url(element, type)` | Get the URL to a specific entity type page (e.g. `info`, `edit`, etc.) | +| `barcode_svg(content, type)` | Generate a barcode SVG from the content and type (e.g. `QRCODE`, `CODE128` etc.). A svg string is returned, which you need to data uri encode to inline it. | + +### Filters + +| Filter name | Description | +|----------------------------------------------|-----------------------------------------------------------------------------------------------| +| `format_bytes` | Format a byte count to a human readable string | +| `format_money(price, currency)` | Format a price to a human readable string with the currency | +| `format_amount(amount, unit)` | Format an amount to a human readable string with the unit object | +| `format_si(value, unit_str)` | Format a value using SI prefixes and the given unit string | +| `placeholders(element)` | Replace all placeholders in a string with the values of the element | ## Use custom fonts for PDF labels -You can use your own fonts for label generation. To do this, put the TTF files of the fonts you want to use into the `assets/fonts/dompdf` folder. -The filename will be used as name for the font family and you can use a `_bold` (or `_b`), `_italic` (or `_i`) or `_bold_italic` (or `_bi`) suffix to define -different styles of the font. So for example, if you copy the file `myfont.ttf` and `myfont_bold.ttf` into the `assets/fonts/dompdf` folder, you can use the font family `myfont` with regular and bold style. -Afterwards regenerate cache with `php bin/console cache:clear`, so the new fonts will be available for label generation. -The fonts will not be availble from the UI directly, you have to use it in the HTML directly either by defining a `style="font-family: 'myfont';"` attribute on the HTML element or by using a CSS class. -You can define the font globally for the label, by adding following statement to the "Additional styles (CSS)" option in the label generator settings: +You can use your own fonts for label generation. To do this, put the TTF files of the fonts you want to use into +the `assets/fonts/dompdf` folder. +The filename will be used as name for the font family, and you can use a `_bold` (or `_b`), `_italic` (or `_i`) +or `_bold_italic` (or `_bi`) suffix to define +different styles of the font. So for example, if you copy the file `myfont.ttf` and `myfont_bold.ttf` into +the `assets/fonts/dompdf` folder, you can use the font family `myfont` with regular and bold style. +Afterward regenerate cache with `php bin/console cache:clear`, so the new fonts will be available for label generation. + +The fonts will not be available from the UI directly, you have to use it in the HTML directly either by defining +a `style="font-family: 'myfont';"` attribute on the HTML element or by using a CSS class. +You can define the font globally for the label, by adding following statement to the "Additional styles (CSS)" option in +the label generator settings: + ```css * { font-family: 'myfont'; @@ -110,9 +275,15 @@ You can define the font globally for the label, by adding following statement to ``` ## Non-latin characters in PDF labels -The default used font (DejaVu) does not support all characters. Especially characters from non-latin languages like Chinese, Japanese, Korean, Arabic, Hebrew, Cyrillic, etc. are not supported. -For this we use [Unifont](http://unifoundry.com/unifont.html) as fallback font. This font supports all (or most) unicode characters, but is not as beautiful as DejaVu. -If you want to use a different (more beautiful) font, you can use the [custom fonts](#use-custom-fonts-for-pdf-labels) feature. -There is the [Noto](https://www.google.com/get/noto/) font family from Google, which supports a lot of languages and is available in different styles (regular, bold, italic, bold-italic). -For example you can use [Noto CJK](https://github.com/notofonts/noto-cjk) for more beautful Chinese, Japanese and Korean characters. \ No newline at end of file +The default used font (DejaVu) does not support all characters. Especially characters from non-latin languages like +Chinese, Japanese, Korean, Arabic, Hebrew, Cyrillic, etc. are not supported. +For this, we use [Unifont](http://unifoundry.com/unifont.html) as fallback font. This font supports all (or most) Unicode +characters but is not as beautiful as DejaVu. + +If you want to use a different (more beautiful) font, you can use the [custom fonts](#use-custom-fonts-for-pdf-labels) +feature. +There is the [Noto](https://www.google.com/get/noto/) font family from Google, which supports a lot of languages and is +available in different styles (regular, bold, italic, bold-italic). +For example, you can use [Noto CJK](https://github.com/notofonts/noto-cjk) for more beautiful Chinese, Japanese, +and Korean characters. \ No newline at end of file diff --git a/docs/usage/tips_tricks.md b/docs/usage/tips_tricks.md index 01ebe33f..cab05620 100644 --- a/docs/usage/tips_tricks.md +++ b/docs/usage/tips_tricks.md @@ -8,42 +8,49 @@ parent: Usage Following you can find miscellaneous tips and tricks for using Part-DB. -## Create datastructures directly from part edit page +## Create data structures directly from part edit page -Instead of first creating a category, manufacturer, footprint, etc. and then creating the part, you can create the -datastructures directly from the part edit page: Just type the name of the datastructure you want to create into the -select field on the part edit page and press "Create new ...". The new datastructure will be created, when you save +Instead of first creating a category, manufacturer, footprint, etc., and then creating the part, you can create the +data structures directly from the part edit page: Just type the name of the data structure you want to create into the +select field on the part edit page and press "Create new ...". The new data structure will be created when you save the part changes. -You can create also create nested datastructures this way. For example, if you want to create a new category "AVRs", +You can create also create nested data structures this way. For example, if you want to create a new category "AVRs", as a subcategory of "MCUs", you can just type "MCUs->AVRs" into the category select field and press "Create new". The new category "AVRs" will be created as a subcategory of "MCUs". If the category "MCUs" does not exist, it will be created too. -## Builtin footprint images -Part-DB includes several builtin images for common footprints. You can use these images in your footprint datastructures, -by creating an attachment on the datastructure and selecting it as preview image. +## Built-in footprint images + +Part-DB includes several built-in images for common footprints. You can use these images in your footprint +data structures, +by creating an attachment on the data structure and selecting it as the preview image. Type the name of the footprint image you want to use into the URL field of the attachment and select it from the -dropdown menu. You can find a gallery of all builtin footprint images and their names in the "Builtin footprint image gallery", -which you can find in the "Tools" menu (you maybe need to give your user the permission to access this tool). +dropdown menu. You can find a gallery of all builtin footprint images and their names in the "Builtin footprint image +gallery", +which you can find in the "Tools" menu (you may need to give your user the permission to access this tool). ## Parametric search -In the "parameters" tab of the filter panel on parts list page, you can define constraints, which parameter values -have to fullfill. This allows you to search for parts with specific parameters (or parameter ranges), for example you + +In the "parameters" tab of the filter panel on parts list page, you can define constraints, and which parameter values +have to fulfill. This allows you to search for parts with specific parameters (or parameter ranges), for example, you can search for all parts with a voltage rating of greater than 5 V. -## View own users permissions +## View own user's permissions + If you want to see which permissions your user has, you can find a list of the permissions in the "Permissions" panel on the user info page. ## Use LaTeX equations -You can use LaTeX equations everywhere where markdown is supported (for example in the description or notes field of a part). + +You can use LaTeX equations everywhere where markdown is supported (for example in the description or notes field of a +part). [KaTeX](https://katex.org/) is used to render the equations. You can find a list of supported features in the [KaTeX documentation](https://katex.org/docs/supported.html). -To input a LaTeX equation, you have to wrap it in a pair of dollar signs (`$`). Single dollar signs mark inline equations, -double dollar signs mark displayed equations (which will be its own line and centered). For example, the following equation -will be rendered as an inline equation: +To input a LaTeX equation, you have to wrap it in a pair of dollar signs (`$`). Single dollar signs mark inline +equations, double dollar signs mark displayed equations (which will be their own line and centered). +For example, the following equation will be rendered as an inline equation: ``` $E=mc^2$ @@ -56,8 +63,9 @@ $$E=mc^2$$ ``` ## Update currency exchange rates automatically -Part-DB can update the currency exchange rates of all defined currencies programatically -by calling the `php bin/console partdb:currencies:update-exchange-rates`. + +Part-DB can update the currency exchange rates of all defined currencies programmatically +by calling `php bin/console partdb:currencies:update-exchange-rates`. If you call this command regularly (e.g. with a cronjob), you can keep the exchange rates up-to-date. @@ -65,18 +73,31 @@ Please note that if you use a base currency, which is not the Euro, you have to free API used by default only supports the Euro as base currency. ## Enforce log comments + On almost any editing operation it is possible to add a comment describing, what or why you changed something. -This comment will be written to change log and can be viewed later. -If you want to enforce your users to add comments to certain operations, you can do this by setting the `ENFORCE_CHANGE_COMMENTS_FOR` option. +This comment will be written to changelog and can be viewed later. +If you want to force your users to add comments to certain operations, you can do this by setting +the `ENFORCE_CHANGE_COMMENTS_FOR` option. See the configuration reference for more information. ## Personal stocks and stock locations -For makerspaces and universities with a lot of users, where each user can have his own stock, which only he should be able to access, you can assign -the user as "owner" of a part lot. This way, only him is allowed to add or remove parts from this lot. -## Update notfications -Part-DB can show you a notification that there is a newer version than currently installed available. The notification will be shown on the homepage and the server info page. -It is only be shown to users which has the `Show available Part-DB updates` permission. +For maker spaces and universities with a lot of users, where each user can have his own stock, which only he should be +able to access, you can assign +the user as "owner" of a part lot. This way, only he is allowed to add or remove parts from this lot. -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 + +Part-DB can show you a notification that there is a newer version than currently installed. The notification +will be shown on the homepage and the server info page. +It is only shown to users which have the `Show available Part-DB updates` permission. + +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`. + +## Internet access via proxy +If your server running Part-DB does not have direct access to the internet, but has to use a proxy server, you can configure +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 00000000..bc4d0bf3 --- /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/Version1.php b/migrations/Version1.php index f1389801..fc4c7b2e 100644 --- a/migrations/Version1.php +++ b/migrations/Version1.php @@ -160,7 +160,7 @@ EOD; 21840,21840,21840,21840,21840,21520,21520,21520,20480,21520,20480, 20480,20480,20480,20480,21504,20480), ( - 2,'admin', '${admin_pw}','','', + 2,'admin', '$admin_pw','','', '','',1,1,21845,21845,21845,21,85,21,349525,21845,21845,21845,21845 ,21845,21845,21845,21845,21845,21845,21845,21845,21845,21845,21845, 21845,21845,21845,21845,21845,21845); @@ -235,4 +235,14 @@ EOD; { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20190902140506.php b/migrations/Version20190902140506.php index 38939b5b..8984cb06 100644 --- a/migrations/Version20190902140506.php +++ b/migrations/Version20190902140506.php @@ -69,6 +69,12 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration //For attachments $this->addSql('DELETE FROM `attachements` WHERE attachements.class_name = "Part" AND (SELECT COUNT(parts.id) FROM parts WHERE parts.id = attachements.element_id) = 0;'); + //Add perms_labels column to groups table if not existing (it was not created in certain legacy versions) + //This prevents the migration failing (see https://github.com/Part-DB/Part-DB-server/issues/366 and https://github.com/Part-DB/Part-DB-server/issues/67) + if (!$this->doesColumnExist('groups', 'perms_labels')) { + $this->addSql('ALTER TABLE `groups` ADD `perms_labels` SMALLINT NOT NULL AFTER `perms_tools`'); + } + /************************************************************************************************************** * Doctrine generated SQL **************************************************************************************************************/ @@ -228,8 +234,8 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration 'orderdetails', 'pricedetails', 'storelocations', 'suppliers', ]; foreach ($tables as $table) { - $this->addSql("UPDATE ${table} SET datetime_added = NOW() WHERE datetime_added = '0000-00-00 00:00:00'"); - $this->addSql("UPDATE ${table} SET last_modified = datetime_added WHERE last_modified = '0000-00-00 00:00:00'"); + $this->addSql("UPDATE $table SET datetime_added = NOW() WHERE datetime_added = '0000-00-00 00:00:00'"); + $this->addSql("UPDATE $table SET last_modified = datetime_added WHERE last_modified = '0000-00-00 00:00:00'"); } //Set the dbVersion to a high value, to prevent the old Part-DB versions to upgrade DB! @@ -374,4 +380,14 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20190913141126.php b/migrations/Version20190913141126.php index 58093338..31eed64a 100644 --- a/migrations/Version20190913141126.php +++ b/migrations/Version20190913141126.php @@ -88,4 +88,14 @@ final class Version20190913141126 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20190924113252.php b/migrations/Version20190924113252.php index fe7c9c8b..8221c686 100644 --- a/migrations/Version20190924113252.php +++ b/migrations/Version20190924113252.php @@ -179,4 +179,14 @@ final class Version20190924113252 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20191214153125.php b/migrations/Version20191214153125.php index 9a61c10d..83803d13 100644 --- a/migrations/Version20191214153125.php +++ b/migrations/Version20191214153125.php @@ -65,4 +65,14 @@ final class Version20191214153125 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20200126191823.php b/migrations/Version20200126191823.php index c093a4d7..cf362ea5 100644 --- a/migrations/Version20200126191823.php +++ b/migrations/Version20200126191823.php @@ -68,4 +68,14 @@ final class Version20200126191823 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20200311204104.php b/migrations/Version20200311204104.php index 2a90b62c..daa6fd28 100644 --- a/migrations/Version20200311204104.php +++ b/migrations/Version20200311204104.php @@ -56,4 +56,14 @@ final class Version20200311204104 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20200409130946.php b/migrations/Version20200409130946.php index 72bdda9c..ef2dc7ab 100644 --- a/migrations/Version20200409130946.php +++ b/migrations/Version20200409130946.php @@ -42,4 +42,14 @@ final class Version20200409130946 extends AbstractMultiPlatformMigration { $this->warnIf(true, "Migration not needed for SQLite. Skipping..."); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20200502161750.php b/migrations/Version20200502161750.php index 93bb0d00..55117541 100644 --- a/migrations/Version20200502161750.php +++ b/migrations/Version20200502161750.php @@ -163,4 +163,14 @@ EOD; $this->addSql('DROP TABLE u2f_keys'); $this->addSql('DROP TABLE "users"'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20220925162725.php b/migrations/Version20220925162725.php index 0f04f04e..21ac8946 100644 --- a/migrations/Version20220925162725.php +++ b/migrations/Version20220925162725.php @@ -535,4 +535,14 @@ final class Version20220925162725 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)'); $this->addSql('CREATE INDEX IDX_1483A5E96DEDCEC2 ON "users" (id_preview_attachement)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20221003212851.php b/migrations/Version20221003212851.php index 3a7379ca..81e33c0e 100644 --- a/migrations/Version20221003212851.php +++ b/migrations/Version20221003212851.php @@ -47,4 +47,14 @@ final class Version20221003212851 extends AbstractMultiPlatformMigration { $this->addSql('DROP TABLE webauthn_keys'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20221114193325.php b/migrations/Version20221114193325.php index d80cc1d2..bc2a97fa 100644 --- a/migrations/Version20221114193325.php +++ b/migrations/Version20221114193325.php @@ -4,24 +4,17 @@ declare(strict_types=1); namespace DoctrineMigrations; -use App\Entity\UserSystem\PermissionData; +use App\Doctrine\Migration\ContainerAwareMigrationInterface; use App\Migration\AbstractMultiPlatformMigration; -use App\Security\Interfaces\HasPermissionsInterface; +use App\Migration\WithPermPresetsTrait; use App\Services\UserSystem\PermissionPresetsHelper; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Schema; -use Doctrine\Migrations\AbstractMigration; use Psr\Log\LoggerInterface; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; -/** - * Auto-generated Migration: Please modify to your needs! - */ -final class Version20221114193325 extends AbstractMultiPlatformMigration implements ContainerAwareInterface +final class Version20221114193325 extends AbstractMultiPlatformMigration implements ContainerAwareMigrationInterface { - private ?ContainerInterface $container = null; - private ?PermissionPresetsHelper $permission_presets_helper = null; + use WithPermPresetsTrait; public function __construct(Connection $connection, LoggerInterface $logger) { @@ -33,34 +26,6 @@ final class Version20221114193325 extends AbstractMultiPlatformMigration impleme return 'Update the permission system to the new system. Please note that all permissions will be reset!'; } - private function getJSONPermDataFromPreset(string $preset): string - { - if ($this->permission_presets_helper === null) { - throw new \RuntimeException('PermissionPresetsHelper not set! There seems to be some issue with the dependency injection!'); - } - - //Create a virtual user on which we can apply the preset - $user = new class implements HasPermissionsInterface { - - public PermissionData $perm_data; - - public function __construct() - { - $this->perm_data = new PermissionData(); - } - - public function getPermissions(): PermissionData - { - return $this->perm_data; - } - }; - - //Apply the preset to the virtual user - $this->permission_presets_helper->applyPreset($user, $preset); - - //And return the json data - return json_encode($user->getPermissions()); - } private function addDataMigrationAndWarning(): void { @@ -83,9 +48,12 @@ final class Version20221114193325 extends AbstractMultiPlatformMigration impleme //Reset the permissions of the admin user, to allow admin permissions (like the admins group) $this->addSql("UPDATE `users` SET permissions_data = '$admin' WHERE id = 2;"); + //This warning should not be needed, anymore, as almost everybody should have updated to the new version by now, and this warning would just irritate new users of the software + /* $this->logger->warning('!!! All permissions were reset! Please change them to the desired state, immediately !!!'); $this->logger->warning('!!! For security reasons all users (except the admin user) were disabled. Login with admin user and reenable other users after checking their permissions !!!'); $this->logger->warning('!!! For more infos see: https://github.com/Part-DB/Part-DB-symfony/discussions/193 !!!'); + */ } public function mySQLUp(Schema $schema): void @@ -161,11 +129,15 @@ final class Version20221114193325 extends AbstractMultiPlatformMigration impleme $this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); } - public function setContainer(ContainerInterface $container = null) + + + public function postgreSQLUp(Schema $schema): void { - if ($container) { - $this->container = $container; - $this->permission_presets_helper = $container->get(PermissionPresetsHelper::class); - } + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); } } diff --git a/migrations/Version20221204004815.php b/migrations/Version20221204004815.php index 20e151db..c6c7b6ab 100644 --- a/migrations/Version20221204004815.php +++ b/migrations/Version20221204004815.php @@ -55,4 +55,14 @@ final class Version20221204004815 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)'); $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20221216224745.php b/migrations/Version20221216224745.php index 63bb535e..9ac3b8ba 100644 --- a/migrations/Version20221216224745.php +++ b/migrations/Version20221216224745.php @@ -67,6 +67,15 @@ final class Version20221216224745 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX log_idx_type ON log (type)'); $this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)'); $this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)'); + } + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); } } diff --git a/migrations/Version20230108165410.php b/migrations/Version20230108165410.php index 4124a95a..90f6314e 100644 --- a/migrations/Version20230108165410.php +++ b/migrations/Version20230108165410.php @@ -320,4 +320,14 @@ final class Version20230108165410 extends AbstractMultiPlatformMigration $this->addSql('ALTER TABLE projects RENAME TO devices'); $this->addSql('ALTER TABLE project_bom_entries RENAME TO device_parts'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230219225340.php b/migrations/Version20230219225340.php index 94e08963..2740c85e 100644 --- a/migrations/Version20230219225340.php +++ b/migrations/Version20230219225340.php @@ -521,4 +521,14 @@ final class Version20230219225340 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + } diff --git a/migrations/Version20230220221024.php b/migrations/Version20230220221024.php index 01d65d51..b2209351 100644 --- a/migrations/Version20230220221024.php +++ b/migrations/Version20230220221024.php @@ -37,4 +37,14 @@ final class Version20230220221024 extends AbstractMultiPlatformMigration { $this->addSql('ALTER TABLE `users` DROP saml_user'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230402170923.php b/migrations/Version20230402170923.php index 016a10d0..3325afb6 100644 --- a/migrations/Version20230402170923.php +++ b/migrations/Version20230402170923.php @@ -297,4 +297,14 @@ final class Version20230402170923 extends AbstractMultiPlatformMigration $this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230408170059.php b/migrations/Version20230408170059.php index 88be7798..e3662e16 100644 --- a/migrations/Version20230408170059.php +++ b/migrations/Version20230408170059.php @@ -49,4 +49,14 @@ final class Version20230408170059 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)'); $this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230408213957.php b/migrations/Version20230408213957.php index 976a79db..47807126 100644 --- a/migrations/Version20230408213957.php +++ b/migrations/Version20230408213957.php @@ -434,4 +434,14 @@ final class Version20230408213957 extends AbstractMultiPlatformMigration $this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230417211732.php b/migrations/Version20230417211732.php index 7fef77eb..5fa3b5f7 100644 --- a/migrations/Version20230417211732.php +++ b/migrations/Version20230417211732.php @@ -45,4 +45,14 @@ final class Version20230417211732 extends AbstractMultiPlatformMigration { //As we done nothing, we don't need to implement this method. } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230528000149.php b/migrations/Version20230528000149.php index 05830937..5e742383 100644 --- a/migrations/Version20230528000149.php +++ b/migrations/Version20230528000149.php @@ -62,4 +62,14 @@ final class Version20230528000149 extends AbstractMultiPlatformMigration $this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230716184033.php b/migrations/Version20230716184033.php index adf3e83c..7c0d8a12 100644 --- a/migrations/Version20230716184033.php +++ b/migrations/Version20230716184033.php @@ -348,4 +348,14 @@ final class Version20230716184033 extends AbstractMultiPlatformMigration $this->addSql('DROP TABLE __temp__webauthn_keys'); $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230730131708.php b/migrations/Version20230730131708.php index 88b49460..a12c3b8d 100644 --- a/migrations/Version20230730131708.php +++ b/migrations/Version20230730131708.php @@ -99,4 +99,14 @@ final class Version20230730131708 extends AbstractMultiPlatformMigration $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); $this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)'); } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } } diff --git a/migrations/Version20230816213201.php b/migrations/Version20230816213201.php new file mode 100644 index 00000000..d776da26 --- /dev/null +++ b/migrations/Version20230816213201.php @@ -0,0 +1,54 @@ +addSql('CREATE TABLE api_tokens (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, valid_until DATETIME DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, UNIQUE INDEX UNIQ_2CAD560E5F37A13B (token), INDEX IDX_2CAD560EA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE api_tokens ADD CONSTRAINT FK_2CAD560EA76ED395 FOREIGN KEY (user_id) REFERENCES `users` (id)'); + } + + public function mySQLDown(Schema $schema): void + { + $this->addSql('ALTER TABLE api_tokens DROP FOREIGN KEY FK_2CAD560EA76ED395'); + $this->addSql('DROP TABLE api_tokens'); + } + + public function sqLiteUp(Schema $schema): void + { + $this->addSql('CREATE TABLE api_tokens (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, valid_until DATETIME DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_2CAD560EA76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_2CAD560E5F37A13B ON api_tokens (token)'); + $this->addSql('CREATE INDEX IDX_2CAD560EA76ED395 ON api_tokens (user_id)'); + } + + public function sqLiteDown(Schema $schema): void + { + $this->addSql('DROP TABLE api_tokens'); + } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } +} diff --git a/migrations/Version20231114223101.php b/migrations/Version20231114223101.php new file mode 100644 index 00000000..c06cb279 --- /dev/null +++ b/migrations/Version20231114223101.php @@ -0,0 +1,81 @@ +addSql('CREATE TABLE part_association (id INT AUTO_INCREMENT NOT NULL, owner_id INT NOT NULL, other_id INT NOT NULL, type SMALLINT NOT NULL, other_type VARCHAR(255) DEFAULT NULL, comment LONGTEXT DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, INDEX IDX_61B952E07E3C61F9 (owner_id), INDEX IDX_61B952E0998D9879 (other_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE part_association ADD CONSTRAINT FK_61B952E07E3C61F9 FOREIGN KEY (owner_id) REFERENCES `parts` (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE part_association ADD CONSTRAINT FK_61B952E0998D9879 FOREIGN KEY (other_id) REFERENCES `parts` (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE part_lots ADD vendor_barcode VARCHAR(255) DEFAULT NULL'); + $this->addSql('CREATE INDEX part_lots_idx_barcode ON part_lots (vendor_barcode)'); + } + + public function mySQLDown(Schema $schema): void + { + $this->addSql('ALTER TABLE part_association DROP FOREIGN KEY FK_61B952E07E3C61F9'); + $this->addSql('ALTER TABLE part_association DROP FOREIGN KEY FK_61B952E0998D9879'); + $this->addSql('DROP TABLE part_association'); + $this->addSql('DROP INDEX part_lots_idx_barcode ON part_lots'); + $this->addSql('ALTER TABLE part_lots DROP vendor_barcode'); + } + + public function sqLiteUp(Schema $schema): void + { + $this->addSql('CREATE TABLE part_association (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, owner_id INTEGER NOT NULL, other_id INTEGER NOT NULL, type SMALLINT NOT NULL, other_type VARCHAR(255) DEFAULT NULL, comment CLOB DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_61B952E07E3C61F9 FOREIGN KEY (owner_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_61B952E0998D9879 FOREIGN KEY (other_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('CREATE INDEX IDX_61B952E07E3C61F9 ON part_association (owner_id)'); + $this->addSql('CREATE INDEX IDX_61B952E0998D9879 ON part_association (other_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__part_lots AS SELECT id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM part_lots'); + $this->addSql('DROP TABLE part_lots'); + $this->addSql('CREATE TABLE part_lots (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_store_location INTEGER DEFAULT NULL, id_part INTEGER NOT NULL, id_owner INTEGER DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, expiration_date DATETIME DEFAULT NULL, instock_unknown BOOLEAN NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, vendor_barcode VARCHAR(255) DEFAULT NULL, CONSTRAINT FK_EBC8F9435D8F4B37 FOREIGN KEY (id_store_location) REFERENCES storelocations (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F943C22F6CC4 FOREIGN KEY (id_part) REFERENCES parts (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F94321E5A74C FOREIGN KEY (id_owner) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO part_lots (id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added) SELECT id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM __temp__part_lots'); + $this->addSql('DROP TABLE __temp__part_lots'); + $this->addSql('CREATE INDEX IDX_EBC8F94321E5A74C ON part_lots (id_owner)'); + $this->addSql('CREATE INDEX IDX_EBC8F943C22F6CC4 ON part_lots (id_part)'); + $this->addSql('CREATE INDEX IDX_EBC8F9435D8F4B37 ON part_lots (id_store_location)'); + $this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)'); + $this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)'); + $this->addSql('CREATE INDEX part_lots_idx_barcode ON part_lots (vendor_barcode)'); + } + + public function sqLiteDown(Schema $schema): void + { + $this->addSql('DROP TABLE part_association'); + $this->addSql('CREATE TEMPORARY TABLE __temp__part_lots AS SELECT id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM part_lots'); + $this->addSql('DROP TABLE part_lots'); + $this->addSql('CREATE TABLE part_lots (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_store_location INTEGER DEFAULT NULL, id_part INTEGER NOT NULL, id_owner INTEGER DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, expiration_date DATETIME DEFAULT NULL, instock_unknown BOOLEAN NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_EBC8F9435D8F4B37 FOREIGN KEY (id_store_location) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F943C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F94321E5A74C FOREIGN KEY (id_owner) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO part_lots (id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added) SELECT id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM __temp__part_lots'); + $this->addSql('DROP TABLE __temp__part_lots'); + $this->addSql('CREATE INDEX IDX_EBC8F9435D8F4B37 ON part_lots (id_store_location)'); + $this->addSql('CREATE INDEX IDX_EBC8F943C22F6CC4 ON part_lots (id_part)'); + $this->addSql('CREATE INDEX IDX_EBC8F94321E5A74C ON part_lots (id_owner)'); + $this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)'); + $this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)'); + } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } +} diff --git a/migrations/Version20231130180903.php b/migrations/Version20231130180903.php new file mode 100644 index 00000000..0eccb5aa --- /dev/null +++ b/migrations/Version20231130180903.php @@ -0,0 +1,98 @@ +addSql('ALTER TABLE categories ADD eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, ADD eda_info_invisible TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_bom TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_board TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_sim TINYINT(1) DEFAULT NULL, ADD eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE footprints ADD eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE parts ADD eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, ADD eda_info_value VARCHAR(255) DEFAULT NULL, ADD eda_info_invisible TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_bom TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_board TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_sim TINYINT(1) DEFAULT NULL, ADD eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, ADD eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL'); + } + + public function mySQLDown(Schema $schema): void + { + $this->addSql('ALTER TABLE `categories` DROP eda_info_reference_prefix, DROP eda_info_invisible, DROP eda_info_exclude_from_bom, DROP eda_info_exclude_from_board, DROP eda_info_exclude_from_sim, DROP eda_info_kicad_symbol'); + $this->addSql('ALTER TABLE `footprints` DROP eda_info_kicad_footprint'); + $this->addSql('ALTER TABLE `parts` DROP eda_info_reference_prefix, DROP eda_info_value, DROP eda_info_invisible, DROP eda_info_exclude_from_bom, DROP eda_info_exclude_from_board, DROP eda_info_exclude_from_sim, DROP eda_info_kicad_symbol, DROP eda_info_kicad_footprint'); + } + + public function sqLiteUp(Schema $schema): void + { + $this->addSql('ALTER TABLE categories ADD COLUMN eda_info_reference_prefix VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE categories ADD COLUMN eda_info_invisible BOOLEAN DEFAULT NULL'); + $this->addSql('ALTER TABLE categories ADD COLUMN eda_info_exclude_from_bom BOOLEAN DEFAULT NULL'); + $this->addSql('ALTER TABLE categories ADD COLUMN eda_info_exclude_from_board BOOLEAN DEFAULT NULL'); + $this->addSql('ALTER TABLE categories ADD COLUMN eda_info_exclude_from_sim BOOLEAN DEFAULT NULL'); + $this->addSql('ALTER TABLE categories ADD COLUMN eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE footprints ADD COLUMN eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE parts ADD COLUMN eda_info_reference_prefix VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE parts ADD COLUMN eda_info_value VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE parts ADD COLUMN eda_info_invisible BOOLEAN DEFAULT NULL'); + $this->addSql('ALTER TABLE parts ADD COLUMN eda_info_exclude_from_bom BOOLEAN DEFAULT NULL'); + $this->addSql('ALTER TABLE parts ADD COLUMN eda_info_exclude_from_board BOOLEAN DEFAULT NULL'); + $this->addSql('ALTER TABLE parts ADD COLUMN eda_info_exclude_from_sim BOOLEAN DEFAULT NULL'); + $this->addSql('ALTER TABLE parts ADD COLUMN eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE parts ADD COLUMN eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL'); + } + + public function sqLiteDown(Schema $schema): void + { + $this->addSql('CREATE TEMPORARY TABLE __temp__categories AS SELECT id, parent_id, id_preview_attachment, name, last_modified, datetime_added, comment, not_selectable, alternative_names, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment FROM "categories"'); + $this->addSql('DROP TABLE "categories"'); + $this->addSql('CREATE TABLE "categories" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, partname_hint CLOB NOT NULL, partname_regex CLOB NOT NULL, disable_footprints BOOLEAN NOT NULL, disable_manufacturers BOOLEAN NOT NULL, disable_autodatasheets BOOLEAN NOT NULL, disable_properties BOOLEAN NOT NULL, default_description CLOB NOT NULL, default_comment CLOB NOT NULL, CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "categories" (id, parent_id, id_preview_attachment, name, last_modified, datetime_added, comment, not_selectable, alternative_names, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment) SELECT id, parent_id, id_preview_attachment, name, last_modified, datetime_added, comment, not_selectable, alternative_names, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment FROM __temp__categories'); + $this->addSql('DROP TABLE __temp__categories'); + $this->addSql('CREATE INDEX IDX_3AF34668727ACA70 ON "categories" (parent_id)'); + $this->addSql('CREATE INDEX IDX_3AF34668EA7100A1 ON "categories" (id_preview_attachment)'); + $this->addSql('CREATE INDEX category_idx_name ON "categories" (name)'); + $this->addSql('CREATE INDEX category_idx_parent_name ON "categories" (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__footprints AS SELECT id, parent_id, id_preview_attachment, id_footprint_3d, name, last_modified, datetime_added, comment, not_selectable, alternative_names FROM "footprints"'); + $this->addSql('DROP TABLE "footprints"'); + $this->addSql('CREATE TABLE "footprints" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_footprint_3d INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_A34D68A2727ACA70 FOREIGN KEY (parent_id) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_A34D68A2EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_A34D68A232A38C34 FOREIGN KEY (id_footprint_3d) REFERENCES "attachments" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "footprints" (id, parent_id, id_preview_attachment, id_footprint_3d, name, last_modified, datetime_added, comment, not_selectable, alternative_names) SELECT id, parent_id, id_preview_attachment, id_footprint_3d, name, last_modified, datetime_added, comment, not_selectable, alternative_names FROM __temp__footprints'); + $this->addSql('DROP TABLE __temp__footprints'); + $this->addSql('CREATE INDEX IDX_A34D68A2727ACA70 ON "footprints" (parent_id)'); + $this->addSql('CREATE INDEX IDX_A34D68A2EA7100A1 ON "footprints" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_A34D68A232A38C34 ON "footprints" (id_footprint_3d)'); + $this->addSql('CREATE INDEX footprint_idx_name ON "footprints" (name)'); + $this->addSql('CREATE INDEX footprint_idx_parent_name ON "footprints" (parent_id, name)'); + $this->addSql('CREATE 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, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated FROM "parts"'); + $this->addSql('DROP TABLE "parts"'); + $this->addSql('CREATE TABLE "parts" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, ipn VARCHAR(100) DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url CLOB NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(255) DEFAULT NULL, provider_reference_last_updated DATETIME DEFAULT NULL, 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)'); + $this->addSql('INSERT INTO "parts" (id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated) SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated FROM __temp__parts'); + $this->addSql('DROP TABLE __temp__parts'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)'); + $this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)'); + $this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint)'); + $this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit)'); + $this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id)'); + $this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)'); + $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); + $this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)'); + } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } +} diff --git a/migrations/Version20240427222442.php b/migrations/Version20240427222442.php new file mode 100644 index 00000000..92b2733d --- /dev/null +++ b/migrations/Version20240427222442.php @@ -0,0 +1,75 @@ +addSql('ALTER TABLE webauthn_keys ADD backup_eligible TINYINT(1) DEFAULT NULL, ADD backup_status TINYINT(1) DEFAULT NULL, ADD uv_initialized TINYINT(1) DEFAULT NULL, ADD last_time_used DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\''); + } + + public function mySQLDown(Schema $schema): void + { + $this->addSql('ALTER TABLE webauthn_keys DROP backup_eligible, DROP backup_status, DROP uv_initialized, DROP last_time_used'); + } + + public function sqLiteUp(Schema $schema): void + { + $this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui FROM webauthn_keys'); + $this->addSql('DROP TABLE webauthn_keys'); + $this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --(DC2Type:base64) + , type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --(DC2Type:array) + , attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --(DC2Type:trust_path) + , aaguid CLOB NOT NULL --(DC2Type:aaguid) + , credential_public_key CLOB NOT NULL --(DC2Type:base64) + , user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, other_ui CLOB DEFAULT NULL --(DC2Type:array) + , backup_eligible BOOLEAN DEFAULT NULL, backup_status BOOLEAN DEFAULT NULL, uv_initialized BOOLEAN DEFAULT NULL, last_time_used DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) + , CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui FROM __temp__webauthn_keys'); + $this->addSql('DROP TABLE __temp__webauthn_keys'); + $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); + } + + public function sqLiteDown(Schema $schema): void + { + $this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, name, last_modified, datetime_added FROM webauthn_keys'); + $this->addSql('DROP TABLE webauthn_keys'); + $this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL -- +(DC2Type:base64) + , type VARCHAR(255) NOT NULL, transports CLOB NOT NULL -- +(DC2Type:array) + , attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL -- +(DC2Type:trust_path) + , aaguid CLOB NOT NULL -- +(DC2Type:aaguid) + , credential_public_key CLOB NOT NULL -- +(DC2Type:base64) + , user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, other_ui CLOB DEFAULT NULL -- +(DC2Type:array) + , name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, name, last_modified, datetime_added FROM __temp__webauthn_keys'); + $this->addSql('DROP TABLE __temp__webauthn_keys'); + $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); + } + + public function postgreSQLUp(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->warnIf(true, "Migration not needed for Postgres. Skipping..."); + } +} diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php new file mode 100644 index 00000000..1c7d2bf9 --- /dev/null +++ b/migrations/Version20240606203053.php @@ -0,0 +1,654 @@ +addSql("CREATE COLLATION numeric (provider = icu, locale = 'en-u-kn-true');"); + + $this->addSql('CREATE TABLE api_tokens (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, valid_until TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_2CAD560E5F37A13B ON api_tokens (token)'); + $this->addSql('CREATE INDEX IDX_2CAD560EA76ED395 ON api_tokens (user_id)'); + $this->addSql('CREATE TABLE "attachment_types" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, filetype_filter TEXT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_EFAED719727ACA70 ON "attachment_types" (parent_id)'); + $this->addSql('CREATE INDEX IDX_EFAED719EA7100A1 ON "attachment_types" (id_preview_attachment)'); + $this->addSql('CREATE INDEX attachment_types_idx_name ON "attachment_types" (name)'); + $this->addSql('CREATE INDEX attachment_types_idx_parent_name ON "attachment_types" (parent_id, name)'); + $this->addSql('CREATE TABLE "attachments" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, original_filename VARCHAR(255) DEFAULT NULL, path VARCHAR(255) NOT NULL, show_in_table BOOLEAN NOT NULL, type_id INT NOT NULL, class_name VARCHAR(255) NOT NULL, element_id INT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_47C4FAD6C54C8C93 ON "attachments" (type_id)'); + $this->addSql('CREATE INDEX IDX_47C4FAD61F1F2A24 ON "attachments" (element_id)'); + $this->addSql('CREATE INDEX attachments_idx_id_element_id_class_name ON "attachments" (id, element_id, class_name)'); + $this->addSql('CREATE INDEX attachments_idx_class_name_id ON "attachments" (class_name, id)'); + $this->addSql('CREATE INDEX attachment_name_idx ON "attachments" (name)'); + $this->addSql('CREATE INDEX attachment_element_idx ON "attachments" (class_name, element_id)'); + $this->addSql('CREATE TABLE "categories" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, partname_hint TEXT NOT NULL, partname_regex TEXT NOT NULL, disable_footprints BOOLEAN NOT NULL, disable_manufacturers BOOLEAN NOT NULL, disable_autodatasheets BOOLEAN NOT NULL, disable_properties BOOLEAN NOT NULL, default_description TEXT NOT NULL, default_comment TEXT NOT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_3AF34668727ACA70 ON "categories" (parent_id)'); + $this->addSql('CREATE INDEX IDX_3AF34668EA7100A1 ON "categories" (id_preview_attachment)'); + $this->addSql('CREATE INDEX category_idx_name ON "categories" (name)'); + $this->addSql('CREATE INDEX category_idx_parent_name ON "categories" (parent_id, name)'); + $this->addSql('CREATE TABLE currencies (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL, iso_code VARCHAR(255) NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)'); + $this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)'); + $this->addSql('CREATE INDEX currency_idx_name ON currencies (name)'); + $this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)'); + $this->addSql('CREATE TABLE "footprints" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, id_footprint_3d INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_A34D68A2727ACA70 ON "footprints" (parent_id)'); + $this->addSql('CREATE INDEX IDX_A34D68A2EA7100A1 ON "footprints" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_A34D68A232A38C34 ON "footprints" (id_footprint_3d)'); + $this->addSql('CREATE INDEX footprint_idx_name ON "footprints" (name)'); + $this->addSql('CREATE INDEX footprint_idx_parent_name ON "footprints" (parent_id, name)'); + $this->addSql('CREATE TABLE "groups" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, permissions_data JSON NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON "groups" (parent_id)'); + $this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON "groups" (id_preview_attachment)'); + $this->addSql('CREATE INDEX group_idx_name ON "groups" (name)'); + $this->addSql('CREATE INDEX group_idx_parent_name ON "groups" (parent_id, name)'); + $this->addSql('CREATE TABLE label_profiles (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, show_in_dropdown BOOLEAN NOT NULL, options_width DOUBLE PRECISION NOT NULL, options_height DOUBLE PRECISION NOT NULL, options_barcode_type VARCHAR(255) NOT NULL, options_picture_type VARCHAR(255) NOT NULL, options_supported_element VARCHAR(255) NOT NULL, options_additional_css TEXT NOT NULL, options_lines_mode VARCHAR(255) NOT NULL, options_lines TEXT NOT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_C93E9CF5EA7100A1 ON label_profiles (id_preview_attachment)'); + $this->addSql('CREATE TABLE log (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, username VARCHAR(255) NOT NULL, datetime TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, level SMALLINT NOT NULL, target_id INT NOT NULL, target_type SMALLINT NOT NULL, extra JSON NOT NULL, id_user INT DEFAULT NULL, type SMALLINT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)'); + $this->addSql('CREATE INDEX log_idx_type ON log (type)'); + $this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)'); + $this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)'); + $this->addSql('CREATE TABLE "manufacturers" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_94565B12727ACA70 ON "manufacturers" (parent_id)'); + $this->addSql('CREATE INDEX IDX_94565B12EA7100A1 ON "manufacturers" (id_preview_attachment)'); + $this->addSql('CREATE INDEX manufacturer_name ON "manufacturers" (name)'); + $this->addSql('CREATE INDEX manufacturer_idx_parent_name ON "manufacturers" (parent_id, name)'); + $this->addSql('CREATE TABLE "measurement_units" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, unit VARCHAR(255) DEFAULT NULL, is_integer BOOLEAN NOT NULL, use_si_prefix BOOLEAN NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_F5AF83CF727ACA70 ON "measurement_units" (parent_id)'); + $this->addSql('CREATE INDEX IDX_F5AF83CFEA7100A1 ON "measurement_units" (id_preview_attachment)'); + $this->addSql('CREATE INDEX unit_idx_name ON "measurement_units" (name)'); + $this->addSql('CREATE INDEX unit_idx_parent_name ON "measurement_units" (parent_id, name)'); + $this->addSql('CREATE TABLE oauth_tokens (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, token TEXT DEFAULT NULL, expires_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, refresh_token TEXT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX oauth_tokens_unique_name ON oauth_tokens (name)'); + $this->addSql('CREATE TABLE "orderdetails" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, supplierpartnr VARCHAR(255) NOT NULL, obsolete BOOLEAN NOT NULL, supplier_product_url TEXT NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, part_id INT NOT NULL, id_supplier INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_489AFCDC4CE34BEC ON "orderdetails" (part_id)'); + $this->addSql('CREATE INDEX IDX_489AFCDCCBF180EB ON "orderdetails" (id_supplier)'); + $this->addSql('CREATE INDEX orderdetails_supplier_part_nr ON "orderdetails" (supplierpartnr)'); + $this->addSql('CREATE TABLE parameters (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, symbol VARCHAR(255) NOT NULL, value_min DOUBLE PRECISION DEFAULT NULL, value_typical DOUBLE PRECISION DEFAULT NULL, value_max DOUBLE PRECISION DEFAULT NULL, unit VARCHAR(255) NOT NULL, value_text VARCHAR(255) NOT NULL, param_group VARCHAR(255) NOT NULL, type SMALLINT NOT NULL, element_id INT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_69348FE1F1F2A24 ON parameters (element_id)'); + $this->addSql('CREATE INDEX parameter_name_idx ON parameters (name)'); + $this->addSql('CREATE INDEX parameter_group_idx ON parameters (param_group)'); + $this->addSql('CREATE INDEX parameter_type_element_idx ON parameters (type, element_id)'); + $this->addSql('CREATE TABLE part_association (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, type SMALLINT NOT NULL, other_type VARCHAR(255) DEFAULT NULL, comment TEXT DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, owner_id INT NOT NULL, other_id INT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_61B952E07E3C61F9 ON part_association (owner_id)'); + $this->addSql('CREATE INDEX IDX_61B952E0998D9879 ON part_association (other_id)'); + $this->addSql('CREATE TABLE part_lots (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, description TEXT NOT NULL, comment TEXT NOT NULL, expiration_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, instock_unknown BOOLEAN NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill BOOLEAN NOT NULL, vendor_barcode VARCHAR(255) DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, id_store_location INT DEFAULT NULL, id_part INT NOT NULL, id_owner INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_EBC8F9435D8F4B37 ON part_lots (id_store_location)'); + $this->addSql('CREATE INDEX IDX_EBC8F943C22F6CC4 ON part_lots (id_part)'); + $this->addSql('CREATE INDEX IDX_EBC8F94321E5A74C ON part_lots (id_owner)'); + $this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)'); + $this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)'); + $this->addSql('CREATE INDEX part_lots_idx_barcode ON part_lots (vendor_barcode)'); + $this->addSql('CREATE TABLE "parts" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags TEXT NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, ipn VARCHAR(100) DEFAULT NULL, description TEXT NOT NULL, comment TEXT NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url TEXT NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INT NOT NULL, manual_order BOOLEAN NOT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(255) DEFAULT NULL, provider_reference_last_updated TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_value VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, id_category INT NOT NULL, id_footprint INT DEFAULT NULL, id_part_unit INT DEFAULT NULL, id_manufacturer INT DEFAULT NULL, order_orderdetails_id INT DEFAULT NULL, built_project_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)'); + $this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)'); + $this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint)'); + $this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit)'); + $this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id)'); + $this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)'); + $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); + $this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)'); + $this->addSql('CREATE TABLE "pricedetails" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, price NUMERIC(11, 5) NOT NULL, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, id_currency INT DEFAULT NULL, orderdetails_id INT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON "pricedetails" (id_currency)'); + $this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON "pricedetails" (orderdetails_id)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount ON "pricedetails" (min_discount_quantity)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON "pricedetails" (min_discount_quantity, price_related_quantity)'); + $this->addSql('CREATE TABLE project_bom_entries (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames TEXT NOT NULL, name VARCHAR(255) DEFAULT NULL, comment TEXT NOT NULL, price NUMERIC(11, 5) DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, id_device INT DEFAULT NULL, id_part INT DEFAULT NULL, price_currency_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)'); + $this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)'); + $this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)'); + $this->addSql('CREATE TABLE projects (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, order_quantity INT NOT NULL, status VARCHAR(64) DEFAULT NULL, order_only_missing_parts BOOLEAN NOT NULL, description TEXT NOT NULL, parent_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_5C93B3A4727ACA70 ON projects (parent_id)'); + $this->addSql('CREATE INDEX IDX_5C93B3A4EA7100A1 ON projects (id_preview_attachment)'); + $this->addSql('CREATE TABLE "storelocations" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, is_full BOOLEAN NOT NULL, only_single_part BOOLEAN NOT NULL, limit_to_existing_parts BOOLEAN NOT NULL, part_owner_must_match BOOLEAN DEFAULT false NOT NULL, parent_id INT DEFAULT NULL, storage_type_id INT DEFAULT NULL, id_owner INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_7517020727ACA70 ON "storelocations" (parent_id)'); + $this->addSql('CREATE INDEX IDX_7517020B270BFF1 ON "storelocations" (storage_type_id)'); + $this->addSql('CREATE INDEX IDX_751702021E5A74C ON "storelocations" (id_owner)'); + $this->addSql('CREATE INDEX IDX_7517020EA7100A1 ON "storelocations" (id_preview_attachment)'); + $this->addSql('CREATE INDEX location_idx_name ON "storelocations" (name)'); + $this->addSql('CREATE INDEX location_idx_parent_name ON "storelocations" (parent_id, name)'); + $this->addSql('CREATE TABLE "suppliers" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, comment TEXT NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names TEXT DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL, parent_id INT DEFAULT NULL, default_currency_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON "suppliers" (id_preview_attachment)'); + $this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)'); + $this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)'); + $this->addSql('CREATE TABLE u2f_keys (key_handle VARCHAR(128) NOT NULL, public_key VARCHAR(255) NOT NULL, certificate TEXT NOT NULL, counter VARCHAR(255) NOT NULL, id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_4F4ADB4BA76ED395 ON u2f_keys (user_id)'); + $this->addSql('CREATE UNIQUE INDEX user_unique ON u2f_keys (user_id, key_handle)'); + $this->addSql('CREATE TABLE "users" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a TEXT NOT NULL, config_instock_comment_w TEXT NOT NULL, about_me TEXT NOT NULL, trusted_device_cookie_version INT NOT NULL, backup_codes JSON NOT NULL, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, show_email_on_profile BOOLEAN DEFAULT false NOT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, settings JSON NOT NULL, backup_codes_generation_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, pw_reset_expires TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, saml_user BOOLEAN NOT NULL, name VARCHAR(180) NOT NULL, permissions_data JSON NOT NULL, group_id INT DEFAULT NULL, id_preview_attachment INT DEFAULT NULL, currency_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)'); + $this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)'); + $this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)'); + $this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); + $this->addSql('CREATE TABLE webauthn_keys (public_key_credential_id TEXT NOT NULL, type VARCHAR(255) NOT NULL, transports TEXT NOT NULL, attestation_type VARCHAR(255) NOT NULL, trust_path JSON NOT NULL, aaguid TEXT NOT NULL, credential_public_key TEXT NOT NULL, user_handle VARCHAR(255) NOT NULL, counter INT NOT NULL, other_ui TEXT DEFAULT NULL, backup_eligible BOOLEAN DEFAULT NULL, backup_status BOOLEAN DEFAULT NULL, uv_initialized BOOLEAN DEFAULT NULL, id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, last_time_used TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); + $this->addSql('ALTER TABLE api_tokens ADD CONSTRAINT FK_2CAD560EA76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "attachment_types" ADD CONSTRAINT FK_EFAED719727ACA70 FOREIGN KEY (parent_id) REFERENCES "attachment_types" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "attachment_types" ADD CONSTRAINT FK_EFAED719EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "attachments" ADD CONSTRAINT FK_47C4FAD6C54C8C93 FOREIGN KEY (type_id) REFERENCES "attachment_types" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "categories" ADD CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "categories" ADD CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE currencies ADD CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE currencies ADD CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "footprints" ADD CONSTRAINT FK_A34D68A2727ACA70 FOREIGN KEY (parent_id) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "footprints" ADD CONSTRAINT FK_A34D68A2EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "footprints" ADD CONSTRAINT FK_A34D68A232A38C34 FOREIGN KEY (id_footprint_3d) REFERENCES "attachments" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "groups" ADD CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "groups" ADD CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE label_profiles ADD CONSTRAINT FK_C93E9CF5EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE log ADD CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "manufacturers" ADD CONSTRAINT FK_94565B12727ACA70 FOREIGN KEY (parent_id) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "manufacturers" ADD CONSTRAINT FK_94565B12EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "measurement_units" ADD CONSTRAINT FK_F5AF83CF727ACA70 FOREIGN KEY (parent_id) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "measurement_units" ADD CONSTRAINT FK_F5AF83CFEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "orderdetails" ADD CONSTRAINT FK_489AFCDC4CE34BEC FOREIGN KEY (part_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "orderdetails" ADD CONSTRAINT FK_489AFCDCCBF180EB FOREIGN KEY (id_supplier) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_association ADD CONSTRAINT FK_61B952E07E3C61F9 FOREIGN KEY (owner_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_association ADD CONSTRAINT FK_61B952E0998D9879 FOREIGN KEY (other_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_lots ADD CONSTRAINT FK_EBC8F9435D8F4B37 FOREIGN KEY (id_store_location) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_lots ADD CONSTRAINT FK_EBC8F943C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE part_lots ADD CONSTRAINT FK_EBC8F94321E5A74C FOREIGN KEY (id_owner) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES "orderdetails" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "parts" ADD CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "pricedetails" ADD CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "pricedetails" ADD CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES "orderdetails" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE project_bom_entries ADD CONSTRAINT FK_1AA2DD312F180363 FOREIGN KEY (id_device) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE project_bom_entries ADD CONSTRAINT FK_1AA2DD31C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE project_bom_entries ADD CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE projects ADD CONSTRAINT FK_5C93B3A4727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE projects ADD CONSTRAINT FK_5C93B3A4EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "storelocations" ADD CONSTRAINT FK_7517020727ACA70 FOREIGN KEY (parent_id) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "storelocations" ADD CONSTRAINT FK_7517020B270BFF1 FOREIGN KEY (storage_type_id) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "storelocations" ADD CONSTRAINT FK_751702021E5A74C FOREIGN KEY (id_owner) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "storelocations" ADD CONSTRAINT FK_7517020EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "suppliers" ADD CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "suppliers" ADD CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "suppliers" ADD CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE u2f_keys ADD CONSTRAINT FK_4F4ADB4BA76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "users" ADD CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "users" ADD CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE "users" ADD CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE webauthn_keys ADD CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + + //Create the initial groups and users + //Retrieve the json representations of the presets + $admin = $this->getJSONPermDataFromPreset(PermissionPresetsHelper::PRESET_ADMIN); + $editor = $this->getJSONPermDataFromPreset(PermissionPresetsHelper::PRESET_EDITOR); + $read_only = $this->getJSONPermDataFromPreset(PermissionPresetsHelper::PRESET_READ_ONLY); + + + $sql = <<addSql($sql); + + //Increase the sequence for the groups, to avoid conflicts later + $this->addSql('SELECT setval(\'groups_id_seq\', 4)'); + + + $admin_pw = $this->getInitalAdminPW(); + + $sql = <<addSql($sql); + + //Increase the sequence for the users, to avoid conflicts later + $this->addSql('SELECT setval(\'users_id_seq\', 3)'); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE api_tokens DROP CONSTRAINT FK_2CAD560EA76ED395'); + $this->addSql('ALTER TABLE "attachment_types" DROP CONSTRAINT FK_EFAED719727ACA70'); + $this->addSql('ALTER TABLE "attachment_types" DROP CONSTRAINT FK_EFAED719EA7100A1'); + $this->addSql('ALTER TABLE "attachments" DROP CONSTRAINT FK_47C4FAD6C54C8C93'); + $this->addSql('ALTER TABLE "categories" DROP CONSTRAINT FK_3AF34668727ACA70'); + $this->addSql('ALTER TABLE "categories" DROP CONSTRAINT FK_3AF34668EA7100A1'); + $this->addSql('ALTER TABLE currencies DROP CONSTRAINT FK_37C44693727ACA70'); + $this->addSql('ALTER TABLE currencies DROP CONSTRAINT FK_37C44693EA7100A1'); + $this->addSql('ALTER TABLE "footprints" DROP CONSTRAINT FK_A34D68A2727ACA70'); + $this->addSql('ALTER TABLE "footprints" DROP CONSTRAINT FK_A34D68A2EA7100A1'); + $this->addSql('ALTER TABLE "footprints" DROP CONSTRAINT FK_A34D68A232A38C34'); + $this->addSql('ALTER TABLE "groups" DROP CONSTRAINT FK_F06D3970727ACA70'); + $this->addSql('ALTER TABLE "groups" DROP CONSTRAINT FK_F06D3970EA7100A1'); + $this->addSql('ALTER TABLE label_profiles DROP CONSTRAINT FK_C93E9CF5EA7100A1'); + $this->addSql('ALTER TABLE log DROP CONSTRAINT FK_8F3F68C56B3CA4B'); + $this->addSql('ALTER TABLE "manufacturers" DROP CONSTRAINT FK_94565B12727ACA70'); + $this->addSql('ALTER TABLE "manufacturers" DROP CONSTRAINT FK_94565B12EA7100A1'); + $this->addSql('ALTER TABLE "measurement_units" DROP CONSTRAINT FK_F5AF83CF727ACA70'); + $this->addSql('ALTER TABLE "measurement_units" DROP CONSTRAINT FK_F5AF83CFEA7100A1'); + $this->addSql('ALTER TABLE "orderdetails" DROP CONSTRAINT FK_489AFCDC4CE34BEC'); + $this->addSql('ALTER TABLE "orderdetails" DROP CONSTRAINT FK_489AFCDCCBF180EB'); + $this->addSql('ALTER TABLE part_association DROP CONSTRAINT FK_61B952E07E3C61F9'); + $this->addSql('ALTER TABLE part_association DROP CONSTRAINT FK_61B952E0998D9879'); + $this->addSql('ALTER TABLE part_lots DROP CONSTRAINT FK_EBC8F9435D8F4B37'); + $this->addSql('ALTER TABLE part_lots DROP CONSTRAINT FK_EBC8F943C22F6CC4'); + $this->addSql('ALTER TABLE part_lots DROP CONSTRAINT FK_EBC8F94321E5A74C'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FEEA7100A1'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE5697F554'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE7E371A10'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE2626CEF9'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE1ECB93AE'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FE81081E9B'); + $this->addSql('ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FEE8AE70D9'); + $this->addSql('ALTER TABLE "pricedetails" DROP CONSTRAINT FK_C68C4459398D64AA'); + $this->addSql('ALTER TABLE "pricedetails" DROP CONSTRAINT FK_C68C44594A01DDC7'); + $this->addSql('ALTER TABLE project_bom_entries DROP CONSTRAINT FK_1AA2DD312F180363'); + $this->addSql('ALTER TABLE project_bom_entries DROP CONSTRAINT FK_1AA2DD31C22F6CC4'); + $this->addSql('ALTER TABLE project_bom_entries DROP CONSTRAINT FK_1AA2DD313FFDCD60'); + $this->addSql('ALTER TABLE projects DROP CONSTRAINT FK_5C93B3A4727ACA70'); + $this->addSql('ALTER TABLE projects DROP CONSTRAINT FK_5C93B3A4EA7100A1'); + $this->addSql('ALTER TABLE "storelocations" DROP CONSTRAINT FK_7517020727ACA70'); + $this->addSql('ALTER TABLE "storelocations" DROP CONSTRAINT FK_7517020B270BFF1'); + $this->addSql('ALTER TABLE "storelocations" DROP CONSTRAINT FK_751702021E5A74C'); + $this->addSql('ALTER TABLE "storelocations" DROP CONSTRAINT FK_7517020EA7100A1'); + $this->addSql('ALTER TABLE "suppliers" DROP CONSTRAINT FK_AC28B95C727ACA70'); + $this->addSql('ALTER TABLE "suppliers" DROP CONSTRAINT FK_AC28B95CECD792C0'); + $this->addSql('ALTER TABLE "suppliers" DROP CONSTRAINT FK_AC28B95CEA7100A1'); + $this->addSql('ALTER TABLE u2f_keys DROP CONSTRAINT FK_4F4ADB4BA76ED395'); + $this->addSql('ALTER TABLE "users" DROP CONSTRAINT FK_1483A5E9FE54D947'); + $this->addSql('ALTER TABLE "users" DROP CONSTRAINT FK_1483A5E9EA7100A1'); + $this->addSql('ALTER TABLE "users" DROP CONSTRAINT FK_1483A5E938248176'); + $this->addSql('ALTER TABLE webauthn_keys DROP CONSTRAINT FK_799FD143A76ED395'); + $this->addSql('DROP TABLE api_tokens'); + $this->addSql('DROP TABLE "attachment_types"'); + $this->addSql('DROP TABLE "attachments"'); + $this->addSql('DROP TABLE "categories"'); + $this->addSql('DROP TABLE currencies'); + $this->addSql('DROP TABLE "footprints"'); + $this->addSql('DROP TABLE "groups"'); + $this->addSql('DROP TABLE label_profiles'); + $this->addSql('DROP TABLE log'); + $this->addSql('DROP TABLE "manufacturers"'); + $this->addSql('DROP TABLE "measurement_units"'); + $this->addSql('DROP TABLE oauth_tokens'); + $this->addSql('DROP TABLE "orderdetails"'); + $this->addSql('DROP TABLE parameters'); + $this->addSql('DROP TABLE part_association'); + $this->addSql('DROP TABLE part_lots'); + $this->addSql('DROP TABLE "parts"'); + $this->addSql('DROP TABLE "pricedetails"'); + $this->addSql('DROP TABLE project_bom_entries'); + $this->addSql('DROP TABLE projects'); + $this->addSql('DROP TABLE "storelocations"'); + $this->addSql('DROP TABLE "suppliers"'); + $this->addSql('DROP TABLE u2f_keys'); + $this->addSql('DROP TABLE "users"'); + $this->addSql('DROP TABLE webauthn_keys'); + } + + public function mySQLUp(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE currencies CHANGE exchange_rate exchange_rate NUMERIC(11, 5) DEFAULT NULL'); + //Set empty JSON fields to "{}" to avoid issues with failing JSON validation + $this->addSql('UPDATE `groups` SET permissions_data = "{}" WHERE permissions_data = ""'); + $this->addSql('ALTER TABLE `groups` CHANGE permissions_data permissions_data JSON NOT NULL'); + //Set the empty JSON fields to "{}" to avoid issues with failing JSON validation + $this->addSql('UPDATE `log` SET extra = "{}" WHERE extra = ""'); + $this->addSql('ALTER TABLE `log` CHANGE level level TINYINT NOT NULL, CHANGE extra extra JSON NOT NULL'); + $this->addSql('ALTER TABLE oauth_tokens CHANGE expires_at expires_at DATETIME DEFAULT NULL'); + $this->addSql('ALTER TABLE pricedetails CHANGE price price NUMERIC(11, 5) NOT NULL'); + $this->addSql('ALTER TABLE project_bom_entries CHANGE price price NUMERIC(11, 5) DEFAULT NULL'); + $this->addSql('ALTER TABLE suppliers CHANGE shipping_costs shipping_costs NUMERIC(11, 5) DEFAULT NULL'); + //Set the empty JSON fields to "{}" to avoid issues with failing JSON validation + $this->addSql('UPDATE `users` SET settings = "{}" WHERE settings = ""'); + $this->addSql('UPDATE `users` SET backup_codes = "{}" WHERE backup_codes = ""'); + $this->addSql('UPDATE `users` SET permissions_data = "{}" WHERE permissions_data = ""'); + $this->addSql('ALTER TABLE `users` CHANGE settings settings JSON NOT NULL, CHANGE backup_codes backup_codes JSON NOT NULL, CHANGE permissions_data permissions_data JSON NOT NULL'); + $this->addSql('ALTER TABLE webauthn_keys CHANGE public_key_credential_id public_key_credential_id LONGTEXT NOT NULL, CHANGE transports transports LONGTEXT NOT NULL, CHANGE trust_path trust_path JSON NOT NULL, CHANGE aaguid aaguid TINYTEXT NOT NULL, CHANGE credential_public_key credential_public_key LONGTEXT NOT NULL, CHANGE other_ui other_ui LONGTEXT DEFAULT NULL, CHANGE last_time_used last_time_used DATETIME DEFAULT NULL'); + + // Add the natural sort emulation function to the database (based on this stackoverflow: https://stackoverflow.com/questions/153633/natural-sort-in-mysql/58154535#58154535) + //This version here is wrong, and will be replaced by the correct version in the next migration (we need to use nowdoc instead of heredoc, otherwise the slashes will be wrongly escaped!!) + $this->addSql(<<0, only the first n numbers in the input string will be converted for nat-sort (so strings that differ only after the first n numbers will not nat-sort amongst themselves). + Total sort-ordering is preserved, i.e. if s1!=s2, then NatSortKey(s1,n)!=NatSortKey(s2,n), for any given n. + Numbers may contain ',' as a thousands separator, and '.' as a decimal point. To reverse these (as appropriate for some European locales), the code would require modification. + Numbers preceded by '+' sort with numbers not preceded with either a '+' or '-' sign. + Negative numbers (preceded with '-') sort before positive numbers, but are sorted in order of ascending absolute value (so -7 sorts BEFORE -1001). + Numbers with leading zeros sort after the same number with no (or fewer) leading zeros. + Decimal-part-only numbers (like .75) are recognised, provided the decimal point is not immediately preceded by either another '.', or by a letter-type character. + Numbers with thousand separators sort after the same number without them. + Thousand separators are only recognised in numbers with no leading zeros that don't immediately follow a ',', and when they format the number correctly. + (When not recognised as a thousand separator, a ',' will instead be treated as separating two distinct numbers). + Version-number-like sequences consisting of 3 or more numbers separated by '.' are treated as distinct entities, and each component number will be nat-sorted. + The entire entity will sort after any number beginning with the first component (so e.g. 10.2.1 sorts after both 10 and 10.995, but before 11) + Note that The first number component in an entity like this is also permitted to contain thousand separators. + + To achieve this, numbers within the input string are prefixed and suffixed according to the following format: + - The number is prefixed by a 2-digit base-36 number representing its length, excluding leading zeros. If there is a decimal point, this length only includes the integer part of the number. + - A 3-character suffix is appended after the number (after the decimals if present). + - The first character is a space, or a '+' sign if the number was preceded by '+'. Any preceding '+' sign is also removed from the front of the number. + - This is followed by a 2-digit base-36 number that encodes the number of leading zeros and whether the number was expressed in comma-separated form (e.g. 1,000,000.25 vs 1000000.25) + - The value of this 2-digit number is: (number of leading zeros)*2 + (1 if comma-separated, 0 otherwise) + - For version number sequences, each component number has the prefix in front of it, and the separating dots are removed. + Then there is a single suffix that consists of a ' ' or '+' character, followed by a pair base-36 digits for each number component in the sequence. + + e.g. here is how some simple sample strings get converted: + 'Foo055' --> 'Foo0255 02' + 'Absolute zero is around -273 centigrade' --> 'Absolute zero is around -03273 00 centigrade' + 'The $1,000,000 prize' --> 'The $071000000 01 prize' + '+99.74 degrees' --> '0299.74+00 degrees' + 'I have 0 apples' --> 'I have 00 02 apples' + '.5 is the same value as 0000.5000' --> '00.5 00 is the same value as 00.5000 08' + 'MariaDB v10.3.0018' --> 'MariaDB v02100130218 000004' + + The restriction to numbers of up to 359 digits comes from the fact that the first character of the base-36 prefix MUST be a decimal digit, and so the highest permitted prefix value is '9Z' or 359 decimal. + The code could be modified to handle longer numbers by increasing the size of (both) the prefix and suffix. + A higher base could also be used (by replacing CONV() with a custom function), provided that the collation you are using sorts the "digits" of the base in the correct order, starting with 0123456789. + However, while the maximum number length may be increased this way, note that the technique this function uses is NOT applicable where strings may contain numbers of unlimited length. + + The function definition does not specify the charset or collation to be used for string-type parameters or variables: The default database charset & collation at the time the function is defined will be used. + This is to make the function code more portable. However, there are some important restrictions: + + - Collation is important here only when comparing (or storing) the output value from this function, but it MUST order the characters " +0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" in that order for the natural sort to work. + This is true for most collations, but not all of them, e.g. in Lithuanian 'Y' comes before 'J' (according to Wikipedia). + To adapt the function to work with such collations, replace CONV() in the function code with a custom function that emits "digits" above 9 that are characters ordered according to the collation in use. + + - For efficiency, the function code uses LENGTH() rather than CHAR_LENGTH() to measure the length of strings that consist only of digits 0-9, '.', and ',' characters. + This works for any single-byte charset, as well as any charset that maps standard ASCII characters to single bytes (such as utf8 or utf8mb4). + If using a charset that maps these characters to multiple bytes (such as, e.g. utf16 or utf32), you MUST replace all instances of LENGTH() in the function definition with CHAR_LENGTH() + + Length of the output: + + Each number converted adds 5 characters (2 prefix + 3 suffix) to the length of the string. n is the maximum count of numbers to convert; + This parameter is provided as a means to limit the maximum output length (to input length + 5*n). + If you do not require the total-ordering property, you could edit the code to use suffixes of 1 character (space or plus) only; this would reduce the maximum output length for any given n. + Since a string of length L has at most ((L+1) DIV 2) individual numbers in it (every 2nd character a digit), for n<=0 the maximum output length is (inputlength + 5*((inputlength+1) DIV 2)) + So for the current input length of 100, the maximum output length is 350. + If changing the input length, the output length must be modified according to the above formula. The DECLARE statements for x,y,r, and suf must also be modified, as the code comments indicate. + ****/ + + DECLARE x,y varchar(1000); # need to be same length as input s + DECLARE r varchar(3500) DEFAULT ''; # return value: needs to be same length as return type + DECLARE suf varchar(1001); # suffix for a number or version string. Must be (((inputlength+1) DIV 2)*2 + 1) chars to support version strings (e.g. '1.2.33.5'), though it's usually just 3 chars. (Max version string e.g. 1.2. ... .5 has ((length of input + 1) DIV 2) numeric components) + DECLARE i,j,k int UNSIGNED; + IF n<=0 THEN SET n := -1; END IF; # n<=0 means "process all numbers" + LOOP + SET i := REGEXP_INSTR(s,'\\d'); # find position of next digit + IF i=0 OR n=0 THEN RETURN CONCAT(r,s); END IF; # no more numbers to process -> we're done + SET n := n-1, suf := ' '; + IF i>1 THEN + IF SUBSTRING(s,i-1,1)='.' AND (i=2 OR SUBSTRING(s,i-2,1) RLIKE '[^.\\p{L}\\p{N}\\p{M}\\x{608}\\x{200C}\\x{200D}\\x{2100}-\\x{214F}\\x{24B6}-\\x{24E9}\\x{1F130}-\\x{1F149}\\x{1F150}-\\x{1F169}\\x{1F170}-\\x{1F189}]') AND (SUBSTRING(s,i) NOT RLIKE '^\\d++\\.\\d') THEN SET i:=i-1; END IF; # Allow decimal number (but not version string) to begin with a '.', provided preceding char is neither another '.', nor a member of the unicode character classes: "Alphabetic", "Letter", "Block=Letterlike Symbols" "Number", "Mark", "Join_Control" + IF i>1 AND SUBSTRING(s,i-1,1)='+' THEN SET suf := '+', j := i-1; ELSE SET j := i; END IF; # move any preceding '+' into the suffix, so equal numbers with and without preceding "+" signs sort together + SET r := CONCAT(r,SUBSTRING(s,1,j-1)); SET s = SUBSTRING(s,i); # add everything before the number to r and strip it from the start of s; preceding '+' is dropped (not included in either r or s) + END IF; + SET x := REGEXP_SUBSTR(s,IF(SUBSTRING(s,1,1) IN ('0','.') OR (SUBSTRING(r,-1)=',' AND suf=' '),'^\\d*+(?:\\.\\d++)*','^(?:[1-9]\\d{0,2}(?:,\\d{3}(?!\\d))++|\\d++)(?:\\.\\d++)*+')); # capture the number + following decimals (including multiple consecutive '.' sequences) + SET s := SUBSTRING(s,CHAR_LENGTH(x)+1); # NOTE: CHAR_LENGTH() can be safely used instead of CHAR_LENGTH() here & below PROVIDED we're using a charset that represents digits, ',' and '.' characters using single bytes (e.g. latin1, utf8) + SET i := INSTR(x,'.'); + IF i=0 THEN SET y := ''; ELSE SET y := SUBSTRING(x,i); SET x := SUBSTRING(x,1,i-1); END IF; # move any following decimals into y + SET i := CHAR_LENGTH(x); + SET x := REPLACE(x,',',''); + SET j := CHAR_LENGTH(x); + SET x := TRIM(LEADING '0' FROM x); # strip leading zeros + SET k := CHAR_LENGTH(x); + SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294) + IF(i=j,0,1),10,36),2,'0')); # (j-k)*2 + IF(i=j,0,1) = (count of leading zeros)*2 + (1 if there are thousands-separators, 0 otherwise) Note the first term is bounded to <= base-36 'ZY' as it must fit within 2 characters + SET i := LOCATE('.',y,2); + IF i=0 THEN + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x,y,suf); # k = count of digits in number, bounded to be <= '9Z' base-36 + ELSE # encode a version number (like 3.12.707, etc) + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x); # k = count of digits in number, bounded to be <= '9Z' base-36 + WHILE CHAR_LENGTH(y)>0 AND n!=0 DO + IF i=0 THEN SET x := SUBSTRING(y,2); SET y := ''; ELSE SET x := SUBSTRING(y,2,i-2); SET y := SUBSTRING(y,i); SET i := LOCATE('.',y,2); END IF; + SET j := CHAR_LENGTH(x); + SET x := TRIM(LEADING '0' FROM x); # strip leading zeros + SET k := CHAR_LENGTH(x); + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x); # k = count of digits in number, bounded to be <= '9Z' base-36 + SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294),10,36),2,'0')); # (j-k)*2 = (count of leading zeros)*2, bounded to fit within 2 base-36 digits + SET n := n-1; + END WHILE; + SET r := CONCAT(r,y,suf); + END IF; + END LOOP; + END + EOD + ); + + } + + public function mySQLDown(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE currencies CHANGE exchange_rate exchange_rate NUMERIC(11, 5) DEFAULT NULL COMMENT \'(DC2Type:big_decimal)\''); + $this->addSql('ALTER TABLE `groups` CHANGE permissions_data permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\''); + $this->addSql('ALTER TABLE log CHANGE level level TINYINT(1) NOT NULL COMMENT \'(DC2Type:tinyint)\', CHANGE extra extra LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\''); + $this->addSql('ALTER TABLE oauth_tokens CHANGE expires_at expires_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE `pricedetails` CHANGE price price NUMERIC(11, 5) NOT NULL COMMENT \'(DC2Type:big_decimal)\''); + $this->addSql('ALTER TABLE project_bom_entries CHANGE price price NUMERIC(11, 5) DEFAULT NULL COMMENT \'(DC2Type:big_decimal)\''); + $this->addSql('ALTER TABLE `suppliers` CHANGE shipping_costs shipping_costs NUMERIC(11, 5) DEFAULT NULL COMMENT \'(DC2Type:big_decimal)\''); + $this->addSql('ALTER TABLE `users` CHANGE backup_codes backup_codes LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', CHANGE settings settings LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', CHANGE permissions_data permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\''); + $this->addSql('ALTER TABLE webauthn_keys CHANGE public_key_credential_id public_key_credential_id LONGTEXT NOT NULL COMMENT \'(DC2Type:base64)\', CHANGE transports transports LONGTEXT NOT NULL COMMENT \'(DC2Type:array)\', CHANGE trust_path trust_path LONGTEXT NOT NULL COMMENT \'(DC2Type:trust_path)\', CHANGE aaguid aaguid TINYTEXT NOT NULL COMMENT \'(DC2Type:aaguid)\', CHANGE credential_public_key credential_public_key LONGTEXT NOT NULL COMMENT \'(DC2Type:base64)\', CHANGE other_ui other_ui LONGTEXT DEFAULT NULL COMMENT \'(DC2Type:array)\', CHANGE last_time_used last_time_used DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\''); + + //Drop custom function + $this->addSql('DROP FUNCTION IF EXISTS NatSortKey'); + } + + public function sqLiteUp(Schema $schema): void + { + $this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM currencies'); + $this->addSql('DROP TABLE currencies'); + $this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL, iso_code VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added, alternative_names) SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM __temp__currencies'); + $this->addSql('DROP TABLE __temp__currencies'); + $this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)'); + $this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)'); + $this->addSql('CREATE INDEX currency_idx_name ON currencies (name)'); + $this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data, alternative_names FROM groups'); + $this->addSql('DROP TABLE groups'); + $this->addSql('CREATE TABLE groups (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO groups (id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data, alternative_names) SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data, alternative_names FROM __temp__groups'); + $this->addSql('DROP TABLE __temp__groups'); + $this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON groups (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON groups (parent_id)'); + $this->addSql('CREATE INDEX group_idx_name ON groups (name)'); + $this->addSql('CREATE INDEX group_idx_parent_name ON groups (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM log'); + $this->addSql('DROP TABLE log'); + $this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, datetime DATETIME NOT NULL, level SMALLINT NOT NULL, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL, type SMALLINT NOT NULL, username VARCHAR(255) NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO log (id, id_user, datetime, level, target_id, target_type, extra, type, username) SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM __temp__log'); + $this->addSql('DROP TABLE __temp__log'); + $this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)'); + $this->addSql('CREATE INDEX log_idx_type ON log (type)'); + $this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)'); + $this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__oauth_tokens AS SELECT id, token, expires_at, refresh_token, name, last_modified, datetime_added FROM oauth_tokens'); + $this->addSql('DROP TABLE oauth_tokens'); + $this->addSql('CREATE TABLE oauth_tokens (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, token CLOB DEFAULT NULL, expires_at DATETIME DEFAULT NULL, refresh_token CLOB DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL)'); + $this->addSql('INSERT INTO oauth_tokens (id, token, expires_at, refresh_token, name, last_modified, datetime_added) SELECT id, token, expires_at, refresh_token, name, last_modified, datetime_added FROM __temp__oauth_tokens'); + $this->addSql('DROP TABLE __temp__oauth_tokens'); + $this->addSql('CREATE UNIQUE INDEX oauth_tokens_unique_name ON oauth_tokens (name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM pricedetails'); + $this->addSql('DROP TABLE pricedetails'); + $this->addSql('CREATE TABLE pricedetails (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO pricedetails (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails'); + $this->addSql('DROP TABLE __temp__pricedetails'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON pricedetails (min_discount_quantity, price_related_quantity)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount ON pricedetails (min_discount_quantity)'); + $this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON pricedetails (id_currency)'); + $this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON pricedetails (orderdetails_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries'); + $this->addSql('DROP TABLE project_bom_entries'); + $this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AFC547992F180363 FOREIGN KEY (id_device) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AFC54799C22F6CC4 FOREIGN KEY (id_part) REFERENCES parts (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries'); + $this->addSql('DROP TABLE __temp__project_bom_entries'); + $this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)'); + $this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)'); + $this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM suppliers'); + $this->addSql('DROP TABLE suppliers'); + $this->addSql('CREATE TABLE suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES suppliers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO suppliers (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM __temp__suppliers'); + $this->addSql('DROP TABLE __temp__suppliers'); + $this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON suppliers (id_preview_attachment)'); + $this->addSql('CREATE INDEX supplier_idx_parent_name ON suppliers (parent_id, name)'); + $this->addSql('CREATE INDEX supplier_idx_name ON suppliers (name)'); + $this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON suppliers (parent_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON suppliers (default_currency_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile FROM users'); + $this->addSql('DROP TABLE users'); + $this->addSql('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB NOT NULL, saml_user BOOLEAN NOT NULL, about_me CLOB NOT NULL, show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO users (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile FROM __temp__users'); + $this->addSql('DROP TABLE __temp__users'); + $this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON users (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON users (currency_id)'); + $this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON users (group_id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON users (name)'); + $this->addSql('CREATE INDEX user_idx_username ON users (name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui, backup_eligible, backup_status, uv_initialized, last_time_used FROM webauthn_keys'); + $this->addSql('DROP TABLE webauthn_keys'); + $this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL, aaguid CLOB NOT NULL, credential_public_key CLOB NOT NULL, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, other_ui CLOB DEFAULT NULL, backup_eligible BOOLEAN DEFAULT NULL, backup_status BOOLEAN DEFAULT NULL, uv_initialized BOOLEAN DEFAULT NULL, last_time_used DATETIME DEFAULT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui, backup_eligible, backup_status, uv_initialized, last_time_used) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui, backup_eligible, backup_status, uv_initialized, last_time_used FROM __temp__webauthn_keys'); + $this->addSql('DROP TABLE __temp__webauthn_keys'); + $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); + } + + public function sqLiteDown(Schema $schema): void + { + $this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, exchange_rate, iso_code, parent_id, id_preview_attachment FROM currencies'); + $this->addSql('DROP TABLE currencies'); + $this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal) + , iso_code VARCHAR(255) NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO currencies (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, exchange_rate, iso_code, parent_id, id_preview_attachment) SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, exchange_rate, iso_code, parent_id, id_preview_attachment FROM __temp__currencies'); + $this->addSql('DROP TABLE __temp__currencies'); + $this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)'); + $this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)'); + $this->addSql('CREATE INDEX currency_idx_name ON currencies (name)'); + $this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, enforce_2fa, permissions_data, parent_id, id_preview_attachment FROM "groups"'); + $this->addSql('DROP TABLE "groups"'); + $this->addSql('CREATE TABLE "groups" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, permissions_data CLOB NOT NULL --(DC2Type:json) + , parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "groups" (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, enforce_2fa, permissions_data, parent_id, id_preview_attachment) SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, enforce_2fa, permissions_data, parent_id, id_preview_attachment FROM __temp__groups'); + $this->addSql('DROP TABLE __temp__groups'); + $this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON "groups" (parent_id)'); + $this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON "groups" (id_preview_attachment)'); + $this->addSql('CREATE INDEX group_idx_name ON "groups" (name)'); + $this->addSql('CREATE INDEX group_idx_parent_name ON "groups" (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, username, datetime, level, target_id, target_type, extra, id_user, type FROM log'); + $this->addSql('DROP TABLE log'); + $this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(255) NOT NULL, datetime DATETIME NOT NULL, level BOOLEAN NOT NULL --(DC2Type:tinyint) + , target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --(DC2Type:json) + , id_user INTEGER DEFAULT NULL, type SMALLINT NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO log (id, username, datetime, level, target_id, target_type, extra, id_user, type) SELECT id, username, datetime, level, target_id, target_type, extra, id_user, type FROM __temp__log'); + $this->addSql('DROP TABLE __temp__log'); + $this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)'); + $this->addSql('CREATE INDEX log_idx_type ON log (type)'); + $this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)'); + $this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__oauth_tokens AS SELECT id, name, last_modified, datetime_added, token, expires_at, refresh_token FROM oauth_tokens'); + $this->addSql('DROP TABLE oauth_tokens'); + $this->addSql('CREATE TABLE oauth_tokens (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, token CLOB DEFAULT NULL, expires_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) + , refresh_token CLOB DEFAULT NULL)'); + $this->addSql('INSERT INTO oauth_tokens (id, name, last_modified, datetime_added, token, expires_at, refresh_token) SELECT id, name, last_modified, datetime_added, token, expires_at, refresh_token FROM __temp__oauth_tokens'); + $this->addSql('DROP TABLE __temp__oauth_tokens'); + $this->addSql('CREATE UNIQUE INDEX oauth_tokens_unique_name ON oauth_tokens (name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added, id_currency, orderdetails_id FROM "pricedetails"'); + $this->addSql('DROP TABLE "pricedetails"'); + $this->addSql('CREATE TABLE "pricedetails" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, price NUMERIC(11, 5) NOT NULL --(DC2Type:big_decimal) + , price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES "orderdetails" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "pricedetails" (id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added, id_currency, orderdetails_id) SELECT id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added, id_currency, orderdetails_id FROM __temp__pricedetails'); + $this->addSql('DROP TABLE __temp__pricedetails'); + $this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON "pricedetails" (id_currency)'); + $this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON "pricedetails" (orderdetails_id)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount ON "pricedetails" (min_discount_quantity)'); + $this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON "pricedetails" (min_discount_quantity, price_related_quantity)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, quantity, mountnames, name, comment, price, last_modified, datetime_added, id_device, id_part, price_currency_id FROM project_bom_entries'); + $this->addSql('DROP TABLE project_bom_entries'); + $this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal) + , last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, CONSTRAINT FK_1AA2DD312F180363 FOREIGN KEY (id_device) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD31C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO project_bom_entries (id, quantity, mountnames, name, comment, price, last_modified, datetime_added, id_device, id_part, price_currency_id) SELECT id, quantity, mountnames, name, comment, price, last_modified, datetime_added, id_device, id_part, price_currency_id FROM __temp__project_bom_entries'); + $this->addSql('DROP TABLE __temp__project_bom_entries'); + $this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)'); + $this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)'); + $this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment FROM "suppliers"'); + $this->addSql('DROP TABLE "suppliers"'); + $this->addSql('CREATE TABLE "suppliers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal) + , parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "suppliers" (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment) SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment FROM __temp__suppliers'); + $this->addSql('DROP TABLE __temp__suppliers'); + $this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON "suppliers" (id_preview_attachment)'); + $this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)'); + $this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, last_modified, datetime_added, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, settings, backup_codes_generation_date, pw_reset_expires, saml_user, name, permissions_data, group_id, id_preview_attachment, currency_id FROM "users"'); + $this->addSql('DROP TABLE "users"'); + $this->addSql('CREATE TABLE "users" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, about_me CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --(DC2Type:json) + , google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, settings CLOB NOT NULL --(DC2Type:json) + , backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, saml_user BOOLEAN NOT NULL, name VARCHAR(180) NOT NULL, permissions_data CLOB NOT NULL --(DC2Type:json) + , group_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "users" (id, last_modified, datetime_added, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, settings, backup_codes_generation_date, pw_reset_expires, saml_user, name, permissions_data, group_id, id_preview_attachment, currency_id) SELECT id, last_modified, datetime_added, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, settings, backup_codes_generation_date, pw_reset_expires, saml_user, name, permissions_data, group_id, id_preview_attachment, currency_id FROM __temp__users'); + $this->addSql('DROP TABLE __temp__users'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)'); + $this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)'); + $this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)'); + $this->addSql('CREATE INDEX user_idx_username ON "users" (name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, backup_eligible, backup_status, uv_initialized, id, name, last_time_used, last_modified, datetime_added, user_id FROM webauthn_keys'); + $this->addSql('DROP TABLE webauthn_keys'); + $this->addSql('CREATE TABLE webauthn_keys (public_key_credential_id CLOB NOT NULL --(DC2Type:base64) + , type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --(DC2Type:array) + , attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --(DC2Type:trust_path) + , aaguid CLOB NOT NULL --(DC2Type:aaguid) + , credential_public_key CLOB NOT NULL --(DC2Type:base64) + , user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, other_ui CLOB DEFAULT NULL --(DC2Type:array) + , backup_eligible BOOLEAN DEFAULT NULL, backup_status BOOLEAN DEFAULT NULL, uv_initialized BOOLEAN DEFAULT NULL, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_time_used DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) + , last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, user_id INTEGER DEFAULT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO webauthn_keys (public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, backup_eligible, backup_status, uv_initialized, id, name, last_time_used, last_modified, datetime_added, user_id) SELECT public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, backup_eligible, backup_status, uv_initialized, id, name, last_time_used, last_modified, datetime_added, user_id FROM __temp__webauthn_keys'); + $this->addSql('DROP TABLE __temp__webauthn_keys'); + $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); + } +} diff --git a/migrations/Version20240728145604.php b/migrations/Version20240728145604.php new file mode 100644 index 00000000..42309d70 --- /dev/null +++ b/migrations/Version20240728145604.php @@ -0,0 +1,165 @@ +addSql('DROP FUNCTION IF EXISTS NatSortKey'); + + //The difference to the original function is the correct length of the suf variable and correct escaping + //We now use heredoc syntax to avoid escaping issues with the \ (which resulted in "range out of order in character class"). + $this->addSql(<<<'EOD' + CREATE DEFINER=CURRENT_USER FUNCTION `NatSortKey`(`s` VARCHAR(1000) CHARSET utf8mb4, `n` INT) RETURNS varchar(3500) CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci + DETERMINISTIC + SQL SECURITY INVOKER + BEGIN + /**** + Converts numbers in the input string s into a format such that sorting results in a nat-sort. + Numbers of up to 359 digits (before the decimal point, if one is present) are supported. Sort results are undefined if the input string contains numbers longer than this. + For n>0, only the first n numbers in the input string will be converted for nat-sort (so strings that differ only after the first n numbers will not nat-sort amongst themselves). + Total sort-ordering is preserved, i.e. if s1!=s2, then NatSortKey(s1,n)!=NatSortKey(s2,n), for any given n. + Numbers may contain ',' as a thousands separator, and '.' as a decimal point. To reverse these (as appropriate for some European locales), the code would require modification. + Numbers preceded by '+' sort with numbers not preceded with either a '+' or '-' sign. + Negative numbers (preceded with '-') sort before positive numbers, but are sorted in order of ascending absolute value (so -7 sorts BEFORE -1001). + Numbers with leading zeros sort after the same number with no (or fewer) leading zeros. + Decimal-part-only numbers (like .75) are recognised, provided the decimal point is not immediately preceded by either another '.', or by a letter-type character. + Numbers with thousand separators sort after the same number without them. + Thousand separators are only recognised in numbers with no leading zeros that don't immediately follow a ',', and when they format the number correctly. + (When not recognised as a thousand separator, a ',' will instead be treated as separating two distinct numbers). + Version-number-like sequences consisting of 3 or more numbers separated by '.' are treated as distinct entities, and each component number will be nat-sorted. + The entire entity will sort after any number beginning with the first component (so e.g. 10.2.1 sorts after both 10 and 10.995, but before 11) + Note that The first number component in an entity like this is also permitted to contain thousand separators. + + To achieve this, numbers within the input string are prefixed and suffixed according to the following format: + - The number is prefixed by a 2-digit base-36 number representing its length, excluding leading zeros. If there is a decimal point, this length only includes the integer part of the number. + - A 3-character suffix is appended after the number (after the decimals if present). + - The first character is a space, or a '+' sign if the number was preceded by '+'. Any preceding '+' sign is also removed from the front of the number. + - This is followed by a 2-digit base-36 number that encodes the number of leading zeros and whether the number was expressed in comma-separated form (e.g. 1,000,000.25 vs 1000000.25) + - The value of this 2-digit number is: (number of leading zeros)*2 + (1 if comma-separated, 0 otherwise) + - For version number sequences, each component number has the prefix in front of it, and the separating dots are removed. + Then there is a single suffix that consists of a ' ' or '+' character, followed by a pair base-36 digits for each number component in the sequence. + + e.g. here is how some simple sample strings get converted: + 'Foo055' --> 'Foo0255 02' + 'Absolute zero is around -273 centigrade' --> 'Absolute zero is around -03273 00 centigrade' + 'The $1,000,000 prize' --> 'The $071000000 01 prize' + '+99.74 degrees' --> '0299.74+00 degrees' + 'I have 0 apples' --> 'I have 00 02 apples' + '.5 is the same value as 0000.5000' --> '00.5 00 is the same value as 00.5000 08' + 'MariaDB v10.3.0018' --> 'MariaDB v02100130218 000004' + + The restriction to numbers of up to 359 digits comes from the fact that the first character of the base-36 prefix MUST be a decimal digit, and so the highest permitted prefix value is '9Z' or 359 decimal. + The code could be modified to handle longer numbers by increasing the size of (both) the prefix and suffix. + A higher base could also be used (by replacing CONV() with a custom function), provided that the collation you are using sorts the "digits" of the base in the correct order, starting with 0123456789. + However, while the maximum number length may be increased this way, note that the technique this function uses is NOT applicable where strings may contain numbers of unlimited length. + + The function definition does not specify the charset or collation to be used for string-type parameters or variables: The default database charset & collation at the time the function is defined will be used. + This is to make the function code more portable. However, there are some important restrictions: + + - Collation is important here only when comparing (or storing) the output value from this function, but it MUST order the characters " +0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" in that order for the natural sort to work. + This is true for most collations, but not all of them, e.g. in Lithuanian 'Y' comes before 'J' (according to Wikipedia). + To adapt the function to work with such collations, replace CONV() in the function code with a custom function that emits "digits" above 9 that are characters ordered according to the collation in use. + + - For efficiency, the function code uses LENGTH() rather than CHAR_LENGTH() to measure the length of strings that consist only of digits 0-9, '.', and ',' characters. + This works for any single-byte charset, as well as any charset that maps standard ASCII characters to single bytes (such as utf8 or utf8mb4). + If using a charset that maps these characters to multiple bytes (such as, e.g. utf16 or utf32), you MUST replace all instances of LENGTH() in the function definition with CHAR_LENGTH() + + Length of the output: + + Each number converted adds 5 characters (2 prefix + 3 suffix) to the length of the string. n is the maximum count of numbers to convert; + This parameter is provided as a means to limit the maximum output length (to input length + 5*n). + If you do not require the total-ordering property, you could edit the code to use suffixes of 1 character (space or plus) only; this would reduce the maximum output length for any given n. + Since a string of length L has at most ((L+1) DIV 2) individual numbers in it (every 2nd character a digit), for n<=0 the maximum output length is (inputlength + 5*((inputlength+1) DIV 2)) + So for the current input length of 100, the maximum output length is 350. + If changing the input length, the output length must be modified according to the above formula. The DECLARE statements for x,y,r, and suf must also be modified, as the code comments indicate. + ****/ + + DECLARE x,y varchar(1000); # need to be same length as input s + DECLARE r varchar(3500) DEFAULT ''; # return value: needs to be same length as return type + DECLARE suf varchar(1001); # suffix for a number or version string. Must be (((inputlength+1) DIV 2)*2 + 1) chars to support version strings (e.g. '1.2.33.5'), though it's usually just 3 chars. (Max version string e.g. 1.2. ... .5 has ((length of input + 1) DIV 2) numeric components) + DECLARE i,j,k int UNSIGNED; + IF n<=0 THEN SET n := -1; END IF; # n<=0 means "process all numbers" + LOOP + SET i := REGEXP_INSTR(s,'\\d'); # find position of next digit + IF i=0 OR n=0 THEN RETURN CONCAT(r,s); END IF; # no more numbers to process -> we're done + SET n := n-1, suf := ' '; + IF i>1 THEN + IF SUBSTRING(s,i-1,1)='.' AND (i=2 OR SUBSTRING(s,i-2,1) RLIKE '[^.\\p{L}\\p{N}\\p{M}\\x{608}\\x{200C}\\x{200D}\\x{2100}-\\x{214F}\\x{24B6}-\\x{24E9}\\x{1F130}-\\x{1F149}\\x{1F150}-\\x{1F169}\\x{1F170}-\\x{1F189}]') AND (SUBSTRING(s,i) NOT RLIKE '^\\d++\\.\\d') THEN SET i:=i-1; END IF; # Allow decimal number (but not version string) to begin with a '.', provided preceding char is neither another '.', nor a member of the unicode character classes: "Alphabetic", "Letter", "Block=Letterlike Symbols" "Number", "Mark", "Join_Control" + IF i>1 AND SUBSTRING(s,i-1,1)='+' THEN SET suf := '+', j := i-1; ELSE SET j := i; END IF; # move any preceding '+' into the suffix, so equal numbers with and without preceding "+" signs sort together + SET r := CONCAT(r,SUBSTRING(s,1,j-1)); SET s = SUBSTRING(s,i); # add everything before the number to r and strip it from the start of s; preceding '+' is dropped (not included in either r or s) + END IF; + SET x := REGEXP_SUBSTR(s,IF(SUBSTRING(s,1,1) IN ('0','.') OR (SUBSTRING(r,-1)=',' AND suf=' '),'^\\d*+(?:\\.\\d++)*','^(?:[1-9]\\d{0,2}(?:,\\d{3}(?!\\d))++|\\d++)(?:\\.\\d++)*+')); # capture the number + following decimals (including multiple consecutive '.' sequences) + SET s := SUBSTRING(s,CHAR_LENGTH(x)+1); # NOTE: CHAR_LENGTH() can be safely used instead of CHAR_LENGTH() here & below PROVIDED we're using a charset that represents digits, ',' and '.' characters using single bytes (e.g. latin1, utf8) + SET i := INSTR(x,'.'); + IF i=0 THEN SET y := ''; ELSE SET y := SUBSTRING(x,i); SET x := SUBSTRING(x,1,i-1); END IF; # move any following decimals into y + SET i := CHAR_LENGTH(x); + SET x := REPLACE(x,',',''); + SET j := CHAR_LENGTH(x); + SET x := TRIM(LEADING '0' FROM x); # strip leading zeros + SET k := CHAR_LENGTH(x); + SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294) + IF(i=j,0,1),10,36),2,'0')); # (j-k)*2 + IF(i=j,0,1) = (count of leading zeros)*2 + (1 if there are thousands-separators, 0 otherwise) Note the first term is bounded to <= base-36 'ZY' as it must fit within 2 characters + SET i := LOCATE('.',y,2); + IF i=0 THEN + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x,y,suf); # k = count of digits in number, bounded to be <= '9Z' base-36 + ELSE # encode a version number (like 3.12.707, etc) + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x); # k = count of digits in number, bounded to be <= '9Z' base-36 + WHILE CHAR_LENGTH(y)>0 AND n!=0 DO + IF i=0 THEN SET x := SUBSTRING(y,2); SET y := ''; ELSE SET x := SUBSTRING(y,2,i-2); SET y := SUBSTRING(y,i); SET i := LOCATE('.',y,2); END IF; + SET j := CHAR_LENGTH(x); + SET x := TRIM(LEADING '0' FROM x); # strip leading zeros + SET k := CHAR_LENGTH(x); + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x); # k = count of digits in number, bounded to be <= '9Z' base-36 + SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294),10,36),2,'0')); # (j-k)*2 = (count of leading zeros)*2, bounded to fit within 2 base-36 digits + SET n := n-1; + END WHILE; + SET r := CONCAT(r,y,suf); + END IF; + END LOOP; + END + EOD + ); + } + + public function mySQLDown(Schema $schema): void + { + //Not needed + } + + public function sqLiteUp(Schema $schema): void + { + //Not needed + } + + public function sqLiteDown(Schema $schema): void + { + //Not needed + } + + public function postgreSQLUp(Schema $schema): void + { + //Not needed + } + + public function postgreSQLDown(Schema $schema): void + { + //Not needed + } +} diff --git a/migrations/Version20250220215048.php b/migrations/Version20250220215048.php new file mode 100644 index 00000000..90a73eb1 --- /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 00000000..57cd3970 --- /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 00000000..14bcb8a9 --- /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/Version20250325073036.php b/migrations/Version20250325073036.php new file mode 100644 index 00000000..3bae80ab --- /dev/null +++ b/migrations/Version20250325073036.php @@ -0,0 +1,307 @@ +addSql(<<<'SQL' + ALTER TABLE categories ADD COLUMN part_ipn_prefix VARCHAR(255) NOT NULL DEFAULT '' + SQL); + } + + public function mySQLDown(Schema $schema): void + { + $this->addSql(<<<'SQL' + ALTER TABLE categories DROP part_ipn_prefix + SQL); + } + + public function sqLiteUp(Schema $schema): void + { + $this->addSql(<<<'SQL' + CREATE TEMPORARY TABLE __temp__categories AS + SELECT + id, + parent_id, + id_preview_attachment, + partname_hint, + partname_regex, + disable_footprints, + disable_manufacturers, + disable_autodatasheets, + disable_properties, + default_description, + default_comment, + comment, + not_selectable, + name, + last_modified, + datetime_added, + alternative_names, + eda_info_reference_prefix, + eda_info_invisible, + eda_info_exclude_from_bom, + eda_info_exclude_from_board, + eda_info_exclude_from_sim, + eda_info_kicad_symbol + FROM categories + SQL); + + $this->addSql('DROP TABLE categories'); + + $this->addSql(<<<'SQL' + CREATE TABLE categories ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + parent_id INTEGER DEFAULT NULL, + id_preview_attachment INTEGER DEFAULT NULL, + partname_hint CLOB NOT NULL, + partname_regex CLOB NOT NULL, + part_ipn_prefix VARCHAR(255) DEFAULT '' NOT NULL, + disable_footprints BOOLEAN NOT NULL, + disable_manufacturers BOOLEAN NOT NULL, + disable_autodatasheets BOOLEAN NOT NULL, + disable_properties BOOLEAN NOT NULL, + default_description CLOB NOT NULL, + default_comment CLOB NOT NULL, + comment CLOB NOT NULL, + not_selectable BOOLEAN NOT NULL, + name VARCHAR(255) NOT NULL, + last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + alternative_names CLOB DEFAULT NULL, + eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, + eda_info_invisible BOOLEAN DEFAULT NULL, + eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, + eda_info_exclude_from_board BOOLEAN DEFAULT NULL, + eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, + eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, + CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE + ) + SQL); + + $this->addSql(<<<'SQL' + INSERT INTO categories ( + id, + parent_id, + id_preview_attachment, + partname_hint, + partname_regex, + disable_footprints, + disable_manufacturers, + disable_autodatasheets, + disable_properties, + default_description, + default_comment, + comment, + not_selectable, + name, + last_modified, + datetime_added, + alternative_names, + eda_info_reference_prefix, + eda_info_invisible, + eda_info_exclude_from_bom, + eda_info_exclude_from_board, + eda_info_exclude_from_sim, + eda_info_kicad_symbol + ) SELECT + id, + parent_id, + id_preview_attachment, + partname_hint, + partname_regex, + disable_footprints, + disable_manufacturers, + disable_autodatasheets, + disable_properties, + default_description, + default_comment, + comment, + not_selectable, + name, + last_modified, + datetime_added, + alternative_names, + eda_info_reference_prefix, + eda_info_invisible, + eda_info_exclude_from_bom, + eda_info_exclude_from_board, + eda_info_exclude_from_sim, + eda_info_kicad_symbol + FROM __temp__categories + SQL); + + $this->addSql('DROP TABLE __temp__categories'); + + $this->addSql(<<<'SQL' + CREATE INDEX IDX_3AF34668727ACA70 ON categories (parent_id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_3AF34668EA7100A1 ON categories (id_preview_attachment) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX category_idx_name ON categories (name) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX category_idx_parent_name ON categories (parent_id, name) + SQL); + } + + public function sqLiteDown(Schema $schema): void + { + $this->addSql(<<<'SQL' + CREATE TEMPORARY TABLE __temp__categories AS + SELECT + id, + parent_id, + id_preview_attachment, + partname_hint, + partname_regex, + disable_footprints, + disable_manufacturers, + disable_autodatasheets, + disable_properties, + default_description, + default_comment, + comment, + not_selectable, + name, + last_modified, + datetime_added, + alternative_names, + eda_info_reference_prefix, + eda_info_invisible, + eda_info_exclude_from_bom, + eda_info_exclude_from_board, + eda_info_exclude_from_sim, + eda_info_kicad_symbol + FROM categories + SQL); + + $this->addSql('DROP TABLE categories'); + + $this->addSql(<<<'SQL' + CREATE TABLE categories ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + parent_id INTEGER DEFAULT NULL, + id_preview_attachment INTEGER DEFAULT NULL, + partname_hint CLOB NOT NULL, + partname_regex CLOB NOT NULL, + disable_footprints BOOLEAN NOT NULL, + disable_manufacturers BOOLEAN NOT NULL, + disable_autodatasheets BOOLEAN NOT NULL, + disable_properties BOOLEAN NOT NULL, + default_description CLOB NOT NULL, + default_comment CLOB NOT NULL, + comment CLOB NOT NULL, + not_selectable BOOLEAN NOT NULL, + name VARCHAR(255) NOT NULL, + last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + alternative_names CLOB DEFAULT NULL, + eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, + eda_info_invisible BOOLEAN DEFAULT NULL, + eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, + eda_info_exclude_from_board BOOLEAN DEFAULT NULL, + eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, + eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, + CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE + ) + SQL); + + $this->addSql(<<<'SQL' + INSERT INTO categories ( + id, + parent_id, + id_preview_attachment, + partname_hint, + partname_regex, + disable_footprints, + disable_manufacturers, + disable_autodatasheets, + disable_properties, + default_description, + default_comment, + comment, + not_selectable, + name, + last_modified, + datetime_added, + alternative_names, + eda_info_reference_prefix, + eda_info_invisible, + eda_info_exclude_from_bom, + eda_info_exclude_from_board, + eda_info_exclude_from_sim, + eda_info_kicad_symbol + ) SELECT + id, + parent_id, + id_preview_attachment, + partname_hint, + partname_regex, + disable_footprints, + disable_manufacturers, + disable_autodatasheets, + disable_properties, + default_description, + default_comment, + comment, + not_selectable, + name, + last_modified, + datetime_added, + alternative_names, + eda_info_reference_prefix, + eda_info_invisible, + eda_info_exclude_from_bom, + eda_info_exclude_from_board, + eda_info_exclude_from_sim, + eda_info_kicad_symbol + FROM __temp__categories + SQL); + + $this->addSql('DROP TABLE __temp__categories'); + + $this->addSql(<<<'SQL' + CREATE INDEX IDX_3AF34668727ACA70 ON categories (parent_id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_3AF34668EA7100A1 ON categories (id_preview_attachment) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX category_idx_name ON categories (name) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX category_idx_parent_name ON categories (parent_id, name) + SQL); + } + + public function postgreSQLUp(Schema $schema): void + { + $this->addSql(<<<'SQL' + ALTER TABLE categories ADD part_ipn_prefix VARCHAR(255) DEFAULT '' NOT NULL + SQL); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->addSql(<<<'SQL' + ALTER TABLE "categories" DROP part_ipn_prefix + SQL); + } +} diff --git a/migrations/Version20250706201121.php b/migrations/Version20250706201121.php new file mode 100644 index 00000000..b7563978 --- /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 00000000..5eb09a77 --- /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 00000000..5b9350b2 --- /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/migrations/Version20251204215443.php b/migrations/Version20251204215443.php new file mode 100644 index 00000000..3cee0035 --- /dev/null +++ b/migrations/Version20251204215443.php @@ -0,0 +1,156 @@ +addSql('ALTER TABLE attachments CHANGE external_path external_path VARCHAR(2048) DEFAULT NULL'); + $this->addSql('ALTER TABLE manufacturers CHANGE website website VARCHAR(2048) NOT NULL, CHANGE auto_product_url auto_product_url VARCHAR(2048) NOT NULL'); + $this->addSql('ALTER TABLE parts CHANGE provider_reference_provider_url provider_reference_provider_url VARCHAR(2048) DEFAULT NULL'); + $this->addSql('ALTER TABLE suppliers CHANGE website website VARCHAR(2048) NOT NULL, CHANGE auto_product_url auto_product_url VARCHAR(2048) NOT NULL'); + } + + public function mySQLDown(Schema $schema): void + { + $this->addSql('ALTER TABLE `attachments` CHANGE external_path external_path VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE `manufacturers` CHANGE website website VARCHAR(255) NOT NULL, CHANGE auto_product_url auto_product_url VARCHAR(255) NOT NULL'); + $this->addSql('ALTER TABLE `parts` CHANGE provider_reference_provider_url provider_reference_provider_url VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE `suppliers` CHANGE website website VARCHAR(255) NOT NULL, CHANGE auto_product_url auto_product_url VARCHAR(255) NOT NULL'); + } + + public function sqLiteUp(Schema $schema): void + { + $this->addSql('CREATE TEMPORARY TABLE __temp__attachments AS SELECT id, type_id, original_filename, show_in_table, name, last_modified, datetime_added, class_name, element_id, internal_path, external_path FROM attachments'); + $this->addSql('DROP TABLE attachments'); + $this->addSql('CREATE TABLE attachments (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, type_id INTEGER NOT NULL, original_filename VARCHAR(255) DEFAULT NULL, show_in_table BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, class_name VARCHAR(255) NOT NULL, element_id INTEGER NOT NULL, internal_path VARCHAR(255) DEFAULT NULL, external_path VARCHAR(2048) DEFAULT NULL, CONSTRAINT FK_47C4FAD6C54C8C93 FOREIGN KEY (type_id) REFERENCES attachment_types (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO attachments (id, type_id, original_filename, show_in_table, name, last_modified, datetime_added, class_name, element_id, internal_path, external_path) SELECT id, type_id, original_filename, show_in_table, name, last_modified, datetime_added, class_name, element_id, internal_path, external_path FROM __temp__attachments'); + $this->addSql('DROP TABLE __temp__attachments'); + $this->addSql('CREATE INDEX attachment_element_idx ON attachments (class_name, element_id)'); + $this->addSql('CREATE INDEX attachment_name_idx ON attachments (name)'); + $this->addSql('CREATE INDEX attachments_idx_class_name_id ON attachments (class_name, id)'); + $this->addSql('CREATE INDEX attachments_idx_id_element_id_class_name ON attachments (id, element_id, class_name)'); + $this->addSql('CREATE INDEX IDX_47C4FAD6C54C8C93 ON attachments (type_id)'); + $this->addSql('CREATE INDEX IDX_47C4FAD61F1F2A24 ON attachments (element_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__manufacturers AS SELECT id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM manufacturers'); + $this->addSql('DROP TABLE manufacturers'); + $this->addSql('CREATE TABLE manufacturers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(2048) NOT NULL, auto_product_url VARCHAR(2048) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_94565B12727ACA70 FOREIGN KEY (parent_id) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_94565B12EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO manufacturers (id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names) SELECT id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM __temp__manufacturers'); + $this->addSql('DROP TABLE __temp__manufacturers'); + $this->addSql('CREATE INDEX IDX_94565B12EA7100A1 ON manufacturers (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_94565B12727ACA70 ON manufacturers (parent_id)'); + $this->addSql('CREATE INDEX manufacturer_name ON manufacturers (name)'); + $this->addSql('CREATE INDEX manufacturer_idx_parent_name ON manufacturers (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__parts AS SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, id_part_custom_state, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint FROM parts'); + $this->addSql('DROP TABLE parts'); + $this->addSql('CREATE TABLE parts (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, id_part_custom_state INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url CLOB NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, ipn VARCHAR(100) DEFAULT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(2048) DEFAULT NULL, provider_reference_last_updated DATETIME DEFAULT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_value VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES footprints (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES part_custom_states (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO parts (id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, id_part_custom_state, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint) SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, id_part_custom_state, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint FROM __temp__parts'); + $this->addSql('DROP TABLE __temp__parts'); + $this->addSql('CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state)'); + $this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON parts (id_manufacturer)'); + $this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON parts (id_part_unit)'); + $this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON parts (id_category)'); + $this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON parts (id_footprint)'); + $this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON parts (id_preview_attachment)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON parts (ipn)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON parts (order_orderdetails_id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON parts (built_project_id)'); + $this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON parts (datetime_added, name, last_modified, id, needs_review)'); + $this->addSql('CREATE INDEX parts_idx_ipn ON parts (ipn)'); + $this->addSql('CREATE INDEX parts_idx_name ON parts (name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM suppliers'); + $this->addSql('DROP TABLE suppliers'); + $this->addSql('CREATE TABLE suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(2048) NOT NULL, auto_product_url VARCHAR(2048) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES suppliers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO suppliers (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM __temp__suppliers'); + $this->addSql('DROP TABLE __temp__suppliers'); + $this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON suppliers (default_currency_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON suppliers (parent_id)'); + $this->addSql('CREATE INDEX supplier_idx_name ON suppliers (name)'); + $this->addSql('CREATE INDEX supplier_idx_parent_name ON suppliers (parent_id, name)'); + $this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON suppliers (id_preview_attachment)'); + } + + public function sqLiteDown(Schema $schema): void + { + $this->addSql('CREATE TEMPORARY TABLE __temp__attachments AS SELECT id, name, last_modified, datetime_added, original_filename, internal_path, external_path, show_in_table, type_id, class_name, element_id FROM "attachments"'); + $this->addSql('DROP TABLE "attachments"'); + $this->addSql('CREATE TABLE "attachments" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, original_filename VARCHAR(255) DEFAULT NULL, internal_path VARCHAR(255) DEFAULT NULL, external_path VARCHAR(255) DEFAULT NULL, show_in_table BOOLEAN NOT NULL, type_id INTEGER NOT NULL, class_name VARCHAR(255) NOT NULL, element_id INTEGER NOT NULL, CONSTRAINT FK_47C4FAD6C54C8C93 FOREIGN KEY (type_id) REFERENCES "attachment_types" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "attachments" (id, name, last_modified, datetime_added, original_filename, internal_path, external_path, show_in_table, type_id, class_name, element_id) SELECT id, name, last_modified, datetime_added, original_filename, internal_path, external_path, show_in_table, type_id, class_name, element_id FROM __temp__attachments'); + $this->addSql('DROP TABLE __temp__attachments'); + $this->addSql('CREATE INDEX IDX_47C4FAD6C54C8C93 ON "attachments" (type_id)'); + $this->addSql('CREATE INDEX IDX_47C4FAD61F1F2A24 ON "attachments" (element_id)'); + $this->addSql('CREATE INDEX attachments_idx_id_element_id_class_name ON "attachments" (id, element_id, class_name)'); + $this->addSql('CREATE INDEX attachments_idx_class_name_id ON "attachments" (class_name, id)'); + $this->addSql('CREATE INDEX attachment_name_idx ON "attachments" (name)'); + $this->addSql('CREATE INDEX attachment_element_idx ON "attachments" (class_name, element_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__manufacturers AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, parent_id, id_preview_attachment FROM "manufacturers"'); + $this->addSql('DROP TABLE "manufacturers"'); + $this->addSql('CREATE TABLE "manufacturers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_94565B12727ACA70 FOREIGN KEY (parent_id) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_94565B12EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "manufacturers" (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, parent_id, id_preview_attachment) SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, parent_id, id_preview_attachment FROM __temp__manufacturers'); + $this->addSql('DROP TABLE __temp__manufacturers'); + $this->addSql('CREATE INDEX IDX_94565B12727ACA70 ON "manufacturers" (parent_id)'); + $this->addSql('CREATE INDEX IDX_94565B12EA7100A1 ON "manufacturers" (id_preview_attachment)'); + $this->addSql('CREATE INDEX manufacturer_name ON "manufacturers" (name)'); + $this->addSql('CREATE INDEX manufacturer_idx_parent_name ON "manufacturers" (parent_id, name)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__parts AS SELECT id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint, id_preview_attachment, id_part_custom_state, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id FROM "parts"'); + $this->addSql('DROP TABLE "parts"'); + $this->addSql('CREATE TABLE "parts" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, ipn VARCHAR(100) DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url CLOB NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(255) DEFAULT NULL, provider_reference_last_updated DATETIME DEFAULT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_value VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_part_custom_state INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES "orderdetails" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "parts" (id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint, id_preview_attachment, id_part_custom_state, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id) SELECT id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint, id_preview_attachment, id_part_custom_state, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id FROM __temp__parts'); + $this->addSql('DROP TABLE __temp__parts'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)'); + $this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment)'); + $this->addSql('CREATE INDEX IDX_6940A7FEA3ED1215 ON "parts" (id_part_custom_state)'); + $this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)'); + $this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint)'); + $this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit)'); + $this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id)'); + $this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)'); + $this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)'); + $this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment FROM "suppliers"'); + $this->addSql('DROP TABLE "suppliers"'); + $this->addSql('CREATE TABLE "suppliers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO "suppliers" (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment) SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment FROM __temp__suppliers'); + $this->addSql('DROP TABLE __temp__suppliers'); + $this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)'); + $this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON "suppliers" (id_preview_attachment)'); + $this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)'); + $this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)'); + } + + public function postgreSQLUp(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE attachments ALTER external_path TYPE VARCHAR(2048)'); + $this->addSql('ALTER TABLE manufacturers ALTER website TYPE VARCHAR(2048)'); + $this->addSql('ALTER TABLE manufacturers ALTER auto_product_url TYPE VARCHAR(2048)'); + $this->addSql('ALTER TABLE parts ALTER provider_reference_provider_url TYPE VARCHAR(2048)'); + $this->addSql('ALTER TABLE suppliers ALTER website TYPE VARCHAR(2048)'); + $this->addSql('ALTER TABLE suppliers ALTER auto_product_url TYPE VARCHAR(2048)'); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->addSql('ALTER TABLE "attachments" ALTER external_path TYPE VARCHAR(255)'); + $this->addSql('ALTER TABLE "manufacturers" ALTER website TYPE VARCHAR(255)'); + $this->addSql('ALTER TABLE "manufacturers" ALTER auto_product_url TYPE VARCHAR(255)'); + $this->addSql('ALTER TABLE "parts" ALTER provider_reference_provider_url TYPE VARCHAR(255)'); + $this->addSql('ALTER TABLE "suppliers" ALTER website TYPE VARCHAR(255)'); + $this->addSql('ALTER TABLE "suppliers" ALTER auto_product_url TYPE VARCHAR(255)'); + } +} diff --git a/package.json b/package.json index e89a2015..a58b3aa4 100644 --- a/package.json +++ b/package.json @@ -2,23 +2,23 @@ "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": "^7.0.1", + "@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": "^4.1.0", + "@symfony/webpack-encore": "^5.1.0", "bootstrap": "^5.1.3", - "core-js": "^3.23.0", + "core-js": "^3.38.0", "intl-messageformat": "^10.2.5", "jquery": "^3.5.1", "popper.js": "^1.14.7", "regenerator-runtime": "^0.13.9", "webpack": "^5.74.0", "webpack-bundle-analyzer": "^4.3.0", - "webpack-cli": "^4.10.0", + "webpack-cli": "^5.1.0", "webpack-notifier": "^1.15.0" }, "license": "AGPL-3.0-or-later", @@ -29,71 +29,49 @@ "watch": "encore dev --watch", "build": "encore production --progress" }, + "engines": { + "node": ">=20.0.0" + }, "dependencies": { - "@ckeditor/ckeditor5-alignment": "^39.0.1", - "@ckeditor/ckeditor5-autoformat": "^39.0.1", - "@ckeditor/ckeditor5-basic-styles": "^39.0.1", - "@ckeditor/ckeditor5-block-quote": "^39.0.1", - "@ckeditor/ckeditor5-code-block": "^39.0.1", - "@ckeditor/ckeditor5-dev-translations": "^38.4.0", - "@ckeditor/ckeditor5-dev-utils": "^38.4.0", - "@ckeditor/ckeditor5-editor-classic": "^39.0.1", - "@ckeditor/ckeditor5-essentials": "^39.0.1", - "@ckeditor/ckeditor5-find-and-replace": "^39.0.1", - "@ckeditor/ckeditor5-font": "^39.0.1", - "@ckeditor/ckeditor5-heading": "^39.0.1", - "@ckeditor/ckeditor5-highlight": "^39.0.1", - "@ckeditor/ckeditor5-horizontal-line": "^39.0.1", - "@ckeditor/ckeditor5-html-embed": "^39.0.1", - "@ckeditor/ckeditor5-html-support": "^39.0.1", - "@ckeditor/ckeditor5-image": "^39.0.1", - "@ckeditor/ckeditor5-indent": "^39.0.1", - "@ckeditor/ckeditor5-link": "^39.0.1", - "@ckeditor/ckeditor5-list": "^39.0.1", - "@ckeditor/ckeditor5-markdown-gfm": "^39.0.1", - "@ckeditor/ckeditor5-media-embed": "^39.0.1", - "@ckeditor/ckeditor5-paragraph": "^39.0.1", - "@ckeditor/ckeditor5-paste-from-office": "^39.0.1", - "@ckeditor/ckeditor5-remove-format": "^39.0.1", - "@ckeditor/ckeditor5-source-editing": "^39.0.1", - "@ckeditor/ckeditor5-special-characters": "^39.0.1", - "@ckeditor/ckeditor5-table": "^39.0.1", - "@ckeditor/ckeditor5-theme-lark": "^39.0.1", - "@ckeditor/ckeditor5-upload": "^39.0.1", - "@ckeditor/ckeditor5-watchdog": "^39.0.1", - "@ckeditor/ckeditor5-word-count": "^39.0.1", + "@algolia/autocomplete-js": "^1.17.0", + "@algolia/autocomplete-plugin-recent-searches": "^1.17.0", + "@algolia/autocomplete-theme-classic": "^1.17.0", + "@ckeditor/ckeditor5-dev-translations": "^43.0.1", + "@ckeditor/ckeditor5-dev-utils": "^43.0.1", "@jbtronics/bs-treeview": "^1.0.1", + "@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": "^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": "^10.0.0", - "datatables.net-bs5": "^1.10.20", - "datatables.net-buttons-bs5": "^2.2.2", - "datatables.net-colreorder-bs5": "^1.5.1", - "datatables.net-fixedheader-bs5": "^3.1.5", - "datatables.net-responsive-bs5": "^2.2.3", - "datatables.net-select-bs5": "^1.2.7", + "compression-webpack-plugin": "^11.1.0", + "datatables.net": "^2.0.0", + "datatables.net-bs5": "^2.0.0", + "datatables.net-buttons-bs5": "^3.0.0", + "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": "^3.0.1", "dompurify": "^3.0.3", - "emoji.json": "^15.0.0", - "exports-loader": "^3.0.0", - "html5-qrcode": "^2.2.1", + "exports-loader": "^5.0.0", "json-formatter-js": "^2.3.4", "jszip": "^3.2.0", "katex": "^0.16.0", - "marked": "^7.0.4", - "marked-gfm-heading-id": "^3.0.4", + "marked": "^16.1.1", + "marked-gfm-heading-id": "^4.1.1", "marked-mangle": "^1.0.1", "pdfmake": "^0.2.2", "stimulus-use": "^0.52.0", "tom-select": "^2.1.0", "ts-loader": "^9.2.6", - "typescript": "^4.0.2" + "typescript": "^5.7.2" } } diff --git a/phpstan.neon b/phpstan.dist.neon similarity index 57% rename from phpstan.neon rename to phpstan.dist.neon index db118378..fc7b3524 100644 --- a/phpstan.neon +++ b/phpstan.dist.neon @@ -10,6 +10,9 @@ parameters: - src/DataTables/Adapter/* - src/Configuration/* - src/Doctrine/Purger/* + - src/DataTables/Adapters/TwoStepORMAdapter.php + - src/Form/Fixes/* + - src/Translation/Fixes/* @@ -17,16 +20,15 @@ parameters: treatPhpDocTypesAsCertain: false symfony: - container_xml_path: '%rootDir%/../../../var/cache/dev/App_KernelDevDebugContainer.xml' + containerXmlPath: '%rootDir%/../../../var/cache/dev/App_KernelDevDebugContainer.xml' + + doctrine: + objectManagerLoader: tests/object-manager.php + allowNullablePropertyForRequiredField: true checkUninitializedProperties: true - checkFunctionNameCase: true - - checkAlwaysTrueInstanceof: false - checkAlwaysTrueCheckTypeFunctionCall: false - checkAlwaysTrueStrictComparison: false - reportAlwaysTrueInLastCondition: false + checkFunctionNameCase: false reportMaybesInPropertyPhpDocTypes: false reportMaybesInMethodSignatures: false @@ -36,20 +38,26 @@ parameters: booleansInConditions: false uselessCast: false requireParentConstructorCall: true - disallowedConstructs: false overwriteVariablesWithLoop: false closureUsesThis: false matchingInheritedMethodNames: true numericOperandsInArithmeticOperators: true - strictCalls: true switchConditionsMatchingType: false noVariableVariables: false + disallowedEmpty: false + disallowedShortTernary: false ignoreErrors: # Ignore errors caused by complex mapping with AbstractStructuralDBElement - '#AbstractStructuralDBElement does not have a field named \$parent#' - - '#AbstractStructuralDBElement does not have a field named \$name#' + #- '#AbstractStructuralDBElement does not have a field named \$name#' # Ignore errors related to the use of the ParametersTrait in Part entity - '#expects .*PartParameter, .*AbstractParameter given.#' - - '#Part::getParameters\(\) should return .*AbstractParameter#' \ No newline at end of file + - '#Part::getParameters\(\) should return .*AbstractParameter#' + + # Ignore doctrine type mapping mismatch + - '#Property .* type mapping mismatch: property can contain .* but database expects .*#' + + # Ignore error of unused WithPermPresetsTrait, as it is used in the migrations which are not analyzed by Phpstan + - '#Trait App\\Migration\\WithPermPresetsTrait is used zero times and is not analysed#' diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 59622803..3feb4940 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,36 +1,42 @@ - - - - - - - - - - - - src - - - - - tests - - - - - - - - + xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" + colors="true" + failOnDeprecation="false" + failOnNotice="true" + failOnWarning="true" + bootstrap="tests/bootstrap.php" + cacheDirectory=".phpunit.cache" + backupGlobals="false" +> + + + + + + + + + + + + + src + + + + + + tests + + + + + + + + + diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index 872169ac..00000000 --- a/psalm.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/.htaccess b/public/.htaccess index ee3b5450..a13baeee 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 d6decff3..00000000 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 c98d3ad4..00000000 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 081386fe..00000000 Binary files a/public/img/calculator/v2.png and /dev/null differ diff --git a/public/img/default_avatar.png b/public/img/default_avatar.png deleted file mode 100644 index 17d6a106..00000000 Binary files a/public/img/default_avatar.png and /dev/null differ diff --git a/public/img/default_avatar.svg b/public/img/default_avatar.svg new file mode 100644 index 00000000..4586017b --- /dev/null +++ b/public/img/default_avatar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/labels/100.png b/public/img/labels/100.png deleted file mode 100644 index f68a23a9..00000000 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 c87e4ceb..00000000 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 68b6594c..00000000 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 2abbd616..00000000 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 34fb8fa8..00000000 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 dd07aa39..00000000 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 a54e16b7..00000000 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 2b57f7d4..00000000 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 28ede43d..00000000 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 1dbb0c61..00000000 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 590f7c5d..00000000 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 ce85ae97..00000000 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 60a90182..00000000 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 24d68d91..00000000 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 97e3c1ef..00000000 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 3e49efee..00000000 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 7e5afd92..00000000 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 554d2a08..00000000 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 d7c1d40f..00000000 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 4a9403af..00000000 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 e63c9248..00000000 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 1d838794..00000000 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 bb4b4248..00000000 --- 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 f0039881..00000000 --- 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 20ea96bf..00000000 --- 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 307439a5..00000000 --- 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 488dafaa..00000000 --- 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 7cb04978..00000000 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 53f51afb..00000000 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 fcfcfd49..00000000 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 211bf5a4..00000000 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 new file mode 100644 index 00000000..8f0f944c --- /dev/null +++ b/public/kicad/footprints.txt @@ -0,0 +1,14213 @@ +# This file contains all the KiCad footprints available in the official library +# Generated by footprints.sh +# 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 +Battery:BatteryHolder_Bulgin_BX0036_1xC +Battery:BatteryHolder_ComfortableElectronic_CH273-2450_1x2450 +Battery:BatteryHolder_Eagle_12BH611-GR +Battery:BatteryHolder_Keystone_103_1x20mm +Battery:BatteryHolder_Keystone_1042_1x18650 +Battery:BatteryHolder_Keystone_104_1x23mm +Battery:BatteryHolder_Keystone_1057_1x2032 +Battery:BatteryHolder_Keystone_1058_1x2032 +Battery:BatteryHolder_Keystone_105_1x2430 +Battery:BatteryHolder_Keystone_1060_1x2032 +Battery:BatteryHolder_Keystone_106_1x20mm +Battery:BatteryHolder_Keystone_107_1x23mm +Battery:BatteryHolder_Keystone_2460_1xAA +Battery:BatteryHolder_Keystone_2462_2xAA +Battery:BatteryHolder_Keystone_2466_1xAAA +Battery:BatteryHolder_Keystone_2468_2xAAA +Battery:BatteryHolder_Keystone_2479_3xAAA +Battery:BatteryHolder_Keystone_2993 +Battery:BatteryHolder_Keystone_2998_1x6.8mm +Battery:BatteryHolder_Keystone_3000_1x12mm +Battery:BatteryHolder_Keystone_3001_1x12mm +Battery:BatteryHolder_Keystone_3002_1x2032 +Battery:BatteryHolder_Keystone_3008_1x2450 +Battery:BatteryHolder_Keystone_3009_1x2450 +Battery:BatteryHolder_Keystone_3034_1x20mm +Battery:BatteryHolder_Keystone_500 +Battery:BatteryHolder_Keystone_590 +Battery:BatteryHolder_LINX_BAT-HLD-012-SMT +Battery:BatteryHolder_MPD_BA9VPC_1xPP3 +Battery:BatteryHolder_MPD_BC12AAPC_2xAA +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 +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 +Battery:Battery_Panasonic_CR2477-VCN_Vertical_CircularHoles +Battery:Battery_Panasonic_CR3032-VCN_Vertical_CircularHoles +Button_Switch_Keyboard:SW_Cherry_MX_1.00u_PCB +Button_Switch_Keyboard:SW_Cherry_MX_1.00u_Plate +Button_Switch_Keyboard:SW_Cherry_MX_1.25u_PCB +Button_Switch_Keyboard:SW_Cherry_MX_1.25u_Plate +Button_Switch_Keyboard:SW_Cherry_MX_1.50u_PCB +Button_Switch_Keyboard:SW_Cherry_MX_1.50u_Plate +Button_Switch_Keyboard:SW_Cherry_MX_1.75u_PCB +Button_Switch_Keyboard:SW_Cherry_MX_1.75u_Plate +Button_Switch_Keyboard:SW_Cherry_MX_2.00u_PCB +Button_Switch_Keyboard:SW_Cherry_MX_2.00u_Plate +Button_Switch_Keyboard:SW_Cherry_MX_2.00u_Vertical_PCB +Button_Switch_Keyboard:SW_Cherry_MX_2.00u_Vertical_Plate +Button_Switch_Keyboard:SW_Cherry_MX_2.25u_PCB +Button_Switch_Keyboard:SW_Cherry_MX_2.25u_Plate +Button_Switch_Keyboard:SW_Cherry_MX_2.75u_PCB +Button_Switch_Keyboard:SW_Cherry_MX_2.75u_Plate +Button_Switch_Keyboard:SW_Cherry_MX_6.25u_PCB +Button_Switch_Keyboard:SW_Cherry_MX_6.25u_Plate +Button_Switch_Keyboard:SW_Cherry_MX_ISOEnter_PCB +Button_Switch_Keyboard:SW_Cherry_MX_ISOEnter_Plate +Button_Switch_Keyboard:SW_Matias_1.00u +Button_Switch_Keyboard:SW_Matias_1.25u +Button_Switch_Keyboard:SW_Matias_1.50u +Button_Switch_Keyboard:SW_Matias_1.75u +Button_Switch_Keyboard:SW_Matias_2.00u +Button_Switch_Keyboard:SW_Matias_2.25u +Button_Switch_Keyboard:SW_Matias_2.75u +Button_Switch_Keyboard:SW_Matias_6.25u +Button_Switch_Keyboard:SW_Matias_ISOEnter +Button_Switch_SMD:Nidec_Copal_CAS-120A +Button_Switch_SMD:Nidec_Copal_SH-7010A +Button_Switch_SMD:Nidec_Copal_SH-7010B +Button_Switch_SMD:Nidec_Copal_SH-7040B +Button_Switch_SMD:Panasonic_EVQPUJ_EVQPUA +Button_Switch_SMD:Panasonic_EVQPUK_EVQPUB +Button_Switch_SMD:Panasonic_EVQPUL_EVQPUC +Button_Switch_SMD:Panasonic_EVQPUM_EVQPUD +Button_Switch_SMD:SW_DIP_SPSTx01_Slide_6.7x4.1mm_W6.73mm_P2.54mm_LowProfile_JPin +Button_Switch_SMD:SW_DIP_SPSTx01_Slide_6.7x4.1mm_W8.61mm_P2.54mm_LowProfile +Button_Switch_SMD:SW_DIP_SPSTx01_Slide_9.78x4.72mm_W8.61mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx01_Slide_Copal_CHS-01A_W5.08mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx01_Slide_Copal_CHS-01B_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx01_Slide_Copal_CVS-01xB_W5.9mm_P1mm +Button_Switch_SMD:SW_DIP_SPSTx01_Slide_Omron_A6S-110x_W8.9mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx02_Slide_6.7x6.64mm_W6.73mm_P2.54mm_LowProfile_JPin +Button_Switch_SMD:SW_DIP_SPSTx02_Slide_6.7x6.64mm_W8.61mm_P2.54mm_LowProfile +Button_Switch_SMD:SW_DIP_SPSTx02_Slide_9.78x7.26mm_W8.61mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx02_Slide_Copal_CHS-02A_W5.08mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx02_Slide_Copal_CHS-02B_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx02_Slide_Copal_CVS-02xB_W5.9mm_P1mm +Button_Switch_SMD:SW_DIP_SPSTx02_Slide_KingTek_DSHP02TJ_W5.25mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx02_Slide_KingTek_DSHP02TS_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx02_Slide_Omron_A6H-2101_W6.15mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx02_Slide_Omron_A6S-210x_W8.9mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx03_Slide_6.7x9.18mm_W6.73mm_P2.54mm_LowProfile_JPin +Button_Switch_SMD:SW_DIP_SPSTx03_Slide_6.7x9.18mm_W8.61mm_P2.54mm_LowProfile +Button_Switch_SMD:SW_DIP_SPSTx03_Slide_9.78x9.8mm_W8.61mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx03_Slide_Copal_CVS-03xB_W5.9mm_P1mm +Button_Switch_SMD:SW_DIP_SPSTx03_Slide_KingTek_DSHP03TJ_W5.25mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx03_Slide_KingTek_DSHP03TS_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx03_Slide_Omron_A6S-310x_W8.9mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx04_Slide_6.7x11.72mm_W6.73mm_P2.54mm_LowProfile_JPin +Button_Switch_SMD:SW_DIP_SPSTx04_Slide_6.7x11.72mm_W8.61mm_P2.54mm_LowProfile +Button_Switch_SMD:SW_DIP_SPSTx04_Slide_9.78x12.34mm_W8.61mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx04_Slide_Copal_CHS-04A_W5.08mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx04_Slide_Copal_CHS-04B_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx04_Slide_Copal_CVS-04xB_W5.9mm_P1mm +Button_Switch_SMD:SW_DIP_SPSTx04_Slide_KingTek_DSHP04TJ_W5.25mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx04_Slide_KingTek_DSHP04TS_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx04_Slide_Omron_A6H-4101_W6.15mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx04_Slide_Omron_A6S-410x_W8.9mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx05_Slide_6.7x14.26mm_W6.73mm_P2.54mm_LowProfile_JPin +Button_Switch_SMD:SW_DIP_SPSTx05_Slide_6.7x14.26mm_W8.61mm_P2.54mm_LowProfile +Button_Switch_SMD:SW_DIP_SPSTx05_Slide_9.78x14.88mm_W8.61mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx05_Slide_KingTek_DSHP05TJ_W5.25mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx05_Slide_KingTek_DSHP05TS_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx05_Slide_Omron_A6S-510x_W8.9mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx06_Slide_6.7x16.8mm_W6.73mm_P2.54mm_LowProfile_JPin +Button_Switch_SMD:SW_DIP_SPSTx06_Slide_6.7x16.8mm_W8.61mm_P2.54mm_LowProfile +Button_Switch_SMD:SW_DIP_SPSTx06_Slide_9.78x17.42mm_W8.61mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx06_Slide_Copal_CHS-06A_W5.08mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx06_Slide_Copal_CHS-06B_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx06_Slide_KingTek_DSHP06TJ_W5.25mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx06_Slide_KingTek_DSHP06TS_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx06_Slide_Omron_A6H-6101_W6.15mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx06_Slide_Omron_A6S-610x_W8.9mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx07_Slide_6.7x19.34mm_W6.73mm_P2.54mm_LowProfile_JPin +Button_Switch_SMD:SW_DIP_SPSTx07_Slide_6.7x19.34mm_W8.61mm_P2.54mm_LowProfile +Button_Switch_SMD:SW_DIP_SPSTx07_Slide_9.78x19.96mm_W8.61mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx07_Slide_KingTek_DSHP07TJ_W5.25mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx07_Slide_KingTek_DSHP07TS_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx07_Slide_Omron_A6S-710x_W8.9mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx08_Slide_6.7x21.88mm_W6.73mm_P2.54mm_LowProfile_JPin +Button_Switch_SMD:SW_DIP_SPSTx08_Slide_6.7x21.88mm_W8.61mm_P2.54mm_LowProfile +Button_Switch_SMD:SW_DIP_SPSTx08_Slide_9.78x22.5mm_W8.61mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx08_Slide_Copal_CHS-08A_W5.08mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx08_Slide_Copal_CHS-08B_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx08_Slide_Copal_CVS-08xB_W5.9mm_P1mm +Button_Switch_SMD:SW_DIP_SPSTx08_Slide_KingTek_DSHP08TJ_W5.25mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx08_Slide_KingTek_DSHP08TS_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx08_Slide_Omron_A6H-8101_W6.15mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx08_Slide_Omron_A6S-810x_W8.9mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx09_Slide_6.7x24.42mm_W6.73mm_P2.54mm_LowProfile_JPin +Button_Switch_SMD:SW_DIP_SPSTx09_Slide_6.7x24.42mm_W8.61mm_P2.54mm_LowProfile +Button_Switch_SMD:SW_DIP_SPSTx09_Slide_9.78x25.04mm_W8.61mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx09_Slide_KingTek_DSHP09TJ_W5.25mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx09_Slide_KingTek_DSHP09TS_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx09_Slide_Omron_A6S-910x_W8.9mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx10_Slide_6.7x26.96mm_W6.73mm_P2.54mm_LowProfile_JPin +Button_Switch_SMD:SW_DIP_SPSTx10_Slide_6.7x26.96mm_W8.61mm_P2.54mm_LowProfile +Button_Switch_SMD:SW_DIP_SPSTx10_Slide_9.78x27.58mm_W8.61mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx10_Slide_Copal_CHS-10A_W5.08mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx10_Slide_Copal_CHS-10B_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx10_Slide_KingTek_DSHP10TJ_W5.25mm_P1.27mm_JPin +Button_Switch_SMD:SW_DIP_SPSTx10_Slide_KingTek_DSHP10TS_W7.62mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx10_Slide_Omron_A6H-10101_W6.15mm_P1.27mm +Button_Switch_SMD:SW_DIP_SPSTx10_Slide_Omron_A6S-1010x_W8.9mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx11_Slide_6.7x29.5mm_W6.73mm_P2.54mm_LowProfile_JPin +Button_Switch_SMD:SW_DIP_SPSTx11_Slide_6.7x29.5mm_W8.61mm_P2.54mm_LowProfile +Button_Switch_SMD:SW_DIP_SPSTx11_Slide_9.78x30.12mm_W8.61mm_P2.54mm +Button_Switch_SMD:SW_DIP_SPSTx12_Slide_6.7x32.04mm_W6.73mm_P2.54mm_LowProfile_JPin +Button_Switch_SMD:SW_DIP_SPSTx12_Slide_6.7x32.04mm_W8.61mm_P2.54mm_LowProfile +Button_Switch_SMD:SW_DIP_SPSTx12_Slide_9.78x32.66mm_W8.61mm_P2.54mm +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_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_PCM12 +Button_Switch_SMD:SW_SPDT_REED_MSDM-DT +Button_Switch_SMD:SW_SPST_B3S-1000 +Button_Switch_SMD:SW_SPST_B3S-1100 +Button_Switch_SMD:SW_SPST_B3SL-1002P +Button_Switch_SMD:SW_SPST_B3SL-1022P +Button_Switch_SMD:SW_SPST_B3U-1000P-B +Button_Switch_SMD:SW_SPST_B3U-1000P +Button_Switch_SMD:SW_SPST_B3U-1100P-B +Button_Switch_SMD:SW_SPST_B3U-1100P +Button_Switch_SMD:SW_SPST_B3U-3000P-B +Button_Switch_SMD:SW_SPST_B3U-3000P +Button_Switch_SMD:SW_SPST_B3U-3100P-B +Button_Switch_SMD:SW_SPST_B3U-3100P +Button_Switch_SMD:SW_SPST_CK_KMS2xxG +Button_Switch_SMD:SW_SPST_CK_KMS2xxGP +Button_Switch_SMD:SW_SPST_CK_KXT3 +Button_Switch_SMD:SW_SPST_CK_RS282G05A3 +Button_Switch_SMD:SW_SPST_EVPBF +Button_Switch_SMD:SW_SPST_EVQP0 +Button_Switch_SMD:SW_SPST_EVQP2 +Button_Switch_SMD:SW_SPST_EVQP7A +Button_Switch_SMD:SW_SPST_EVQP7C +Button_Switch_SMD:SW_SPST_EVQPE1 +Button_Switch_SMD:SW_SPST_EVQQ2 +Button_Switch_SMD:SW_SPST_FSMSM +Button_Switch_SMD:SW_SPST_Omron_B3FS-100xP +Button_Switch_SMD:SW_SPST_Omron_B3FS-101xP +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 +Button_Switch_SMD:SW_SPST_REED_CT10-XXXX-G1 +Button_Switch_SMD:SW_SPST_REED_CT10-XXXX-G2 +Button_Switch_SMD:SW_SPST_REED_CT10-XXXX-G4 +Button_Switch_SMD:SW_SPST_SKQG_WithoutStem +Button_Switch_SMD:SW_SPST_SKQG_WithStem +Button_Switch_SMD:SW_SPST_TL3305A +Button_Switch_SMD:SW_SPST_TL3305B +Button_Switch_SMD:SW_SPST_TL3305C +Button_Switch_SMD:SW_SPST_TL3342 +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_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 +Button_Switch_THT:SW_DIP_SPSTx01_Slide_9.78x4.72mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx02_Piano_10.8x6.64mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx02_Piano_CTS_Series194-2MSTN_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx02_Slide_6.7x6.64mm_W7.62mm_P2.54mm_LowProfile +Button_Switch_THT:SW_DIP_SPSTx02_Slide_9.78x7.26mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx03_Piano_10.8x9.18mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx03_Piano_CTS_Series194-3MSTN_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx03_Slide_6.7x9.18mm_W7.62mm_P2.54mm_LowProfile +Button_Switch_THT:SW_DIP_SPSTx03_Slide_9.78x9.8mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx04_Piano_10.8x11.72mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx04_Piano_CTS_Series194-4MSTN_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx04_Slide_6.7x11.72mm_W7.62mm_P2.54mm_LowProfile +Button_Switch_THT:SW_DIP_SPSTx04_Slide_9.78x12.34mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx05_Piano_10.8x14.26mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx05_Piano_CTS_Series194-5MSTN_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx05_Slide_6.7x14.26mm_W7.62mm_P2.54mm_LowProfile +Button_Switch_THT:SW_DIP_SPSTx05_Slide_9.78x14.88mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx06_Piano_10.8x16.8mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx06_Piano_CTS_Series194-6MSTN_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx06_Slide_6.7x16.8mm_W7.62mm_P2.54mm_LowProfile +Button_Switch_THT:SW_DIP_SPSTx06_Slide_9.78x17.42mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx07_Piano_10.8x19.34mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx07_Piano_CTS_Series194-7MSTN_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx07_Slide_6.7x19.34mm_W7.62mm_P2.54mm_LowProfile +Button_Switch_THT:SW_DIP_SPSTx07_Slide_9.78x19.96mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx08_Piano_10.8x21.88mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx08_Piano_CTS_Series194-8MSTN_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx08_Slide_6.7x21.88mm_W7.62mm_P2.54mm_LowProfile +Button_Switch_THT:SW_DIP_SPSTx08_Slide_9.78x22.5mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx09_Piano_10.8x24.42mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx09_Piano_CTS_Series194-9MSTN_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx09_Slide_6.7x24.42mm_W7.62mm_P2.54mm_LowProfile +Button_Switch_THT:SW_DIP_SPSTx09_Slide_9.78x25.04mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx10_Piano_10.8x26.96mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx10_Piano_CTS_Series194-10MSTN_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx10_Slide_6.7x26.96mm_W7.62mm_P2.54mm_LowProfile +Button_Switch_THT:SW_DIP_SPSTx10_Slide_9.78x27.58mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx11_Piano_10.8x29.5mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx11_Piano_CTS_Series194-11MSTN_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx11_Slide_6.7x29.5mm_W7.62mm_P2.54mm_LowProfile +Button_Switch_THT:SW_DIP_SPSTx11_Slide_9.78x30.12mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx12_Piano_10.8x32.04mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx12_Piano_CTS_Series194-12MSTN_W7.62mm_P2.54mm +Button_Switch_THT:SW_DIP_SPSTx12_Slide_6.7x32.04mm_W7.62mm_P2.54mm_LowProfile +Button_Switch_THT:SW_DIP_SPSTx12_Slide_9.78x32.66mm_W7.62mm_P2.54mm +Button_Switch_THT:SW_E-Switch_EG1224_SPDT_Angled +Button_Switch_THT:SW_E-Switch_EG1271_SPDT +Button_Switch_THT:SW_E-Switch_EG2219_DPDT_Angled +Button_Switch_THT:SW_Lever_1P2T_NKK_GW12LxH +Button_Switch_THT:SW_MEC_5GTH9 +Button_Switch_THT:SW_NKK_BB15AH +Button_Switch_THT:SW_NKK_G1xJP +Button_Switch_THT:SW_NKK_GW12LJP +Button_Switch_THT:SW_NKK_NR01 +Button_Switch_THT:SW_PUSH-12mm +Button_Switch_THT:SW_PUSH-12mm_Wuerth-430476085716 +Button_Switch_THT:SW_PUSH_1P1T_6x3.5mm_H4.3_APEM_MJTP1243 +Button_Switch_THT:SW_PUSH_1P1T_6x3.5mm_H5.0_APEM_MJTP1250 +Button_Switch_THT:SW_Push_1P1T_NO_LED_E-Switch_TL1250 +Button_Switch_THT:SW_Push_1P2T_Vertical_E-Switch_800UDP8P1A1M6 +Button_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH1xxxxxxV2 +Button_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH2xxxxxxV2 +Button_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH3xxxxxxV2 +Button_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH4xxxxxxV2 +Button_Switch_THT:SW_Push_2P2T_Toggle_CK_PVA2OAH5xxxxxxV2 +Button_Switch_THT:SW_Push_2P2T_Toggle_CK_PVA2xxH1xxxxxxV2 +Button_Switch_THT:SW_Push_2P2T_Toggle_CK_PVA2xxH2xxxxxxV2 +Button_Switch_THT:SW_Push_2P2T_Toggle_CK_PVA2xxH3xxxxxxV2 +Button_Switch_THT:SW_Push_2P2T_Toggle_CK_PVA2xxH4xxxxxxV2 +Button_Switch_THT:SW_Push_2P2T_Vertical_E-Switch_800UDP8P1A1M6 +Button_Switch_THT:SW_PUSH_6mm +Button_Switch_THT:SW_PUSH_6mm_H13mm +Button_Switch_THT:SW_PUSH_6mm_H4.3mm +Button_Switch_THT:SW_PUSH_6mm_H5mm +Button_Switch_THT:SW_PUSH_6mm_H7.3mm +Button_Switch_THT:SW_PUSH_6mm_H8.5mm +Button_Switch_THT:SW_PUSH_6mm_H8mm +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-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 +Button_Switch_THT:SW_Tactile_SPST_Angled_PTS645Vx58-2LFS +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 +Buzzer_Beeper:Buzzer_D14mm_H7mm_P10mm +Buzzer_Beeper:Buzzer_Mallory_AST1109MLTRQ +Buzzer_Beeper:Buzzer_Mallory_AST1240MLQ +Buzzer_Beeper:Buzzer_Murata_PKLCS1212E +Buzzer_Beeper:Buzzer_Murata_PKMCS0909E +Buzzer_Beeper:Buzzer_TDK_PS1240P02BT_D12.2mm_H6.5mm +Buzzer_Beeper:Indicator_PUI_AI-1440-TWT-24V-2-R +Buzzer_Beeper:MagneticBuzzer_CUI_CMT-8504-100-SMT +Buzzer_Beeper:MagneticBuzzer_CUI_CST-931RP-A +Buzzer_Beeper:MagneticBuzzer_Kingstate_KCG0601 +Buzzer_Beeper:MagneticBuzzer_Kobitone_254-EMB73-RO +Buzzer_Beeper:MagneticBuzzer_Kobitone_254-EMB84Q-RO +Buzzer_Beeper:MagneticBuzzer_ProjectsUnlimited_AI-4228-TWT-R +Buzzer_Beeper:MagneticBuzzer_ProSignal_ABI-009-RC +Buzzer_Beeper:MagneticBuzzer_ProSignal_ABI-010-RC +Buzzer_Beeper:MagneticBuzzer_ProSignal_ABT-410-RC +Buzzer_Beeper:MagneticBuzzer_PUI_AT-0927-TT-6-R +Buzzer_Beeper:MagneticBuzzer_PUI_SMT-1028-T-2-R +Buzzer_Beeper:MagneticBuzzer_StarMicronics_HMB-06_HMB-12 +Buzzer_Beeper:PUIAudio_SMT_0825_S_4_R +Buzzer_Beeper:Speaker_CUI_CMR-1206S-67 +Calibration_Scale:Gauge_100mm_Grid_Type1_CopperTop +Calibration_Scale:Gauge_100mm_Type1_CopperTop +Calibration_Scale:Gauge_100mm_Type1_SilkScreenTop +Calibration_Scale:Gauge_100mm_Type2_CopperTop +Calibration_Scale:Gauge_100mm_Type2_SilkScreenTop +Calibration_Scale:Gauge_10mm_Type1_CopperTop +Calibration_Scale:Gauge_10mm_Type1_SilkScreenTop +Calibration_Scale:Gauge_10mm_Type2_CopperTop +Calibration_Scale:Gauge_10mm_Type2_SilkScreenTop +Calibration_Scale:Gauge_10mm_Type3_CopperTop +Calibration_Scale:Gauge_10mm_Type3_SilkScreenTop +Calibration_Scale:Gauge_10mm_Type4_CopperTop +Calibration_Scale:Gauge_10mm_Type4_SilkScreenTop +Calibration_Scale:Gauge_10mm_Type5_CopperTop +Calibration_Scale:Gauge_10mm_Type5_SilkScreenTop +Calibration_Scale:Gauge_50mm_Type1_CopperTop +Calibration_Scale:Gauge_50mm_Type1_SilkScreenTop +Calibration_Scale:Gauge_50mm_Type2_CopperTop +Calibration_Scale:Gauge_50mm_Type2_SilkScreenTop +Capacitor_SMD:CP_Elec_10x10.5 +Capacitor_SMD:CP_Elec_10x10 +Capacitor_SMD:CP_Elec_10x12.5 +Capacitor_SMD:CP_Elec_10x12.6 +Capacitor_SMD:CP_Elec_10x14.3 +Capacitor_SMD:CP_Elec_10x7.7 +Capacitor_SMD:CP_Elec_10x7.9 +Capacitor_SMD:CP_Elec_16x17.5 +Capacitor_SMD:CP_Elec_16x22 +Capacitor_SMD:CP_Elec_18x17.5 +Capacitor_SMD:CP_Elec_18x22 +Capacitor_SMD:CP_Elec_3x5.3 +Capacitor_SMD:CP_Elec_3x5.4 +Capacitor_SMD:CP_Elec_4x3.9 +Capacitor_SMD:CP_Elec_4x3 +Capacitor_SMD:CP_Elec_4x4.5 +Capacitor_SMD:CP_Elec_4x5.3 +Capacitor_SMD:CP_Elec_4x5.4 +Capacitor_SMD:CP_Elec_4x5.7 +Capacitor_SMD:CP_Elec_4x5.8 +Capacitor_SMD:CP_Elec_5x3.9 +Capacitor_SMD:CP_Elec_5x3 +Capacitor_SMD:CP_Elec_5x4.4 +Capacitor_SMD:CP_Elec_5x4.5 +Capacitor_SMD:CP_Elec_5x5.3 +Capacitor_SMD:CP_Elec_5x5.4 +Capacitor_SMD:CP_Elec_5x5.7 +Capacitor_SMD:CP_Elec_5x5.8 +Capacitor_SMD:CP_Elec_5x5.9 +Capacitor_SMD:CP_Elec_6.3x3.9 +Capacitor_SMD:CP_Elec_6.3x3 +Capacitor_SMD:CP_Elec_6.3x4.5 +Capacitor_SMD:CP_Elec_6.3x4.9 +Capacitor_SMD:CP_Elec_6.3x5.2 +Capacitor_SMD:CP_Elec_6.3x5.3 +Capacitor_SMD:CP_Elec_6.3x5.4 +Capacitor_SMD:CP_Elec_6.3x5.4_Nichicon +Capacitor_SMD:CP_Elec_6.3x5.7 +Capacitor_SMD:CP_Elec_6.3x5.8 +Capacitor_SMD:CP_Elec_6.3x5.9 +Capacitor_SMD:CP_Elec_6.3x7.7 +Capacitor_SMD:CP_Elec_6.3x9.9 +Capacitor_SMD:CP_Elec_8x10.5 +Capacitor_SMD:CP_Elec_8x10 +Capacitor_SMD:CP_Elec_8x11.9 +Capacitor_SMD:CP_Elec_8x5.4 +Capacitor_SMD:CP_Elec_8x6.2 +Capacitor_SMD:CP_Elec_8x6.5 +Capacitor_SMD:CP_Elec_8x6.7 +Capacitor_SMD:CP_Elec_8x6.9 +Capacitor_SMD:CP_Elec_CAP-XX_DMF3Zxxxxxxxx3D +Capacitor_SMD:C_01005_0402Metric +Capacitor_SMD:C_01005_0402Metric_Pad0.57x0.30mm_HandSolder +Capacitor_SMD:C_0201_0603Metric +Capacitor_SMD:C_0201_0603Metric_Pad0.64x0.40mm_HandSolder +Capacitor_SMD:C_0402_1005Metric +Capacitor_SMD:C_0402_1005Metric_Pad0.74x0.62mm_HandSolder +Capacitor_SMD:C_0504_1310Metric +Capacitor_SMD:C_0504_1310Metric_Pad0.83x1.28mm_HandSolder +Capacitor_SMD:C_0603_1608Metric +Capacitor_SMD:C_0603_1608Metric_Pad1.08x0.95mm_HandSolder +Capacitor_SMD:C_0805_2012Metric +Capacitor_SMD:C_0805_2012Metric_Pad1.18x1.45mm_HandSolder +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 +Capacitor_SMD:C_1825_4564Metric_Pad1.57x6.80mm_HandSolder +Capacitor_SMD:C_2220_5750Metric +Capacitor_SMD:C_2220_5750Metric_Pad1.97x5.40mm_HandSolder +Capacitor_SMD:C_2225_5664Metric +Capacitor_SMD:C_2225_5664Metric_Pad1.80x6.60mm_HandSolder +Capacitor_SMD:C_3640_9110Metric +Capacitor_SMD:C_3640_9110Metric_Pad2.10x10.45mm_HandSolder +Capacitor_SMD:C_Elec_10x10.2 +Capacitor_SMD:C_Elec_3x5.4 +Capacitor_SMD:C_Elec_4x5.4 +Capacitor_SMD:C_Elec_4x5.8 +Capacitor_SMD:C_Elec_5x5.4 +Capacitor_SMD:C_Elec_5x5.8 +Capacitor_SMD:C_Elec_6.3x5.4 +Capacitor_SMD:C_Elec_6.3x5.8 +Capacitor_SMD:C_Elec_6.3x7.7 +Capacitor_SMD:C_Elec_8x10.2 +Capacitor_SMD:C_Elec_8x5.4 +Capacitor_SMD:C_Elec_8x6.2 +Capacitor_SMD:C_Trimmer_Murata_TZB4-A +Capacitor_SMD:C_Trimmer_Murata_TZB4-B +Capacitor_SMD:C_Trimmer_Murata_TZC3 +Capacitor_SMD:C_Trimmer_Murata_TZR1 +Capacitor_SMD:C_Trimmer_Murata_TZW4 +Capacitor_SMD:C_Trimmer_Murata_TZY2 +Capacitor_SMD:C_Trimmer_Sprague-Goodman_SGC3 +Capacitor_SMD:C_Trimmer_Voltronics_JN +Capacitor_SMD:C_Trimmer_Voltronics_JQ +Capacitor_SMD:C_Trimmer_Voltronics_JR +Capacitor_SMD:C_Trimmer_Voltronics_JV +Capacitor_SMD:C_Trimmer_Voltronics_JZ +Capacitor_Tantalum_SMD:CP_EIA-1608-08_AVX-J +Capacitor_Tantalum_SMD:CP_EIA-1608-08_AVX-J_Pad1.25x1.05mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-1608-10_AVX-L +Capacitor_Tantalum_SMD:CP_EIA-1608-10_AVX-L_Pad1.25x1.05mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-2012-12_Kemet-R +Capacitor_Tantalum_SMD:CP_EIA-2012-12_Kemet-R_Pad1.30x1.05mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-2012-15_AVX-P +Capacitor_Tantalum_SMD:CP_EIA-2012-15_AVX-P_Pad1.30x1.05mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-3216-10_Kemet-I +Capacitor_Tantalum_SMD:CP_EIA-3216-10_Kemet-I_Pad1.58x1.35mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-3216-12_Kemet-S +Capacitor_Tantalum_SMD:CP_EIA-3216-12_Kemet-S_Pad1.58x1.35mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-3216-18_Kemet-A +Capacitor_Tantalum_SMD:CP_EIA-3216-18_Kemet-A_Pad1.58x1.35mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-3528-12_Kemet-T +Capacitor_Tantalum_SMD:CP_EIA-3528-12_Kemet-T_Pad1.50x2.35mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-3528-15_AVX-H +Capacitor_Tantalum_SMD:CP_EIA-3528-15_AVX-H_Pad1.50x2.35mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-3528-21_Kemet-B +Capacitor_Tantalum_SMD:CP_EIA-3528-21_Kemet-B_Pad1.50x2.35mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-6032-15_Kemet-U +Capacitor_Tantalum_SMD:CP_EIA-6032-15_Kemet-U_Pad2.25x2.35mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-6032-20_AVX-F +Capacitor_Tantalum_SMD:CP_EIA-6032-20_AVX-F_Pad2.25x2.35mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-6032-28_Kemet-C +Capacitor_Tantalum_SMD:CP_EIA-6032-28_Kemet-C_Pad2.25x2.35mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7132-20_AVX-U +Capacitor_Tantalum_SMD:CP_EIA-7132-20_AVX-U_Pad2.72x3.50mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7132-28_AVX-C +Capacitor_Tantalum_SMD:CP_EIA-7132-28_AVX-C_Pad2.72x3.50mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7260-15_AVX-R +Capacitor_Tantalum_SMD:CP_EIA-7260-15_AVX-R_Pad2.68x6.30mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7260-20_AVX-M +Capacitor_Tantalum_SMD:CP_EIA-7260-20_AVX-M_Pad2.68x6.30mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7260-28_AVX-M +Capacitor_Tantalum_SMD:CP_EIA-7260-28_AVX-M_Pad2.68x6.30mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7260-38_AVX-R +Capacitor_Tantalum_SMD:CP_EIA-7260-38_AVX-R_Pad2.68x6.30mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7343-15_Kemet-W +Capacitor_Tantalum_SMD:CP_EIA-7343-15_Kemet-W_Pad2.25x2.55mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7343-20_Kemet-V +Capacitor_Tantalum_SMD:CP_EIA-7343-20_Kemet-V_Pad2.25x2.55mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7343-30_AVX-N +Capacitor_Tantalum_SMD:CP_EIA-7343-30_AVX-N_Pad2.25x2.55mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7343-31_Kemet-D +Capacitor_Tantalum_SMD:CP_EIA-7343-31_Kemet-D_Pad2.25x2.55mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7343-40_Kemet-Y +Capacitor_Tantalum_SMD:CP_EIA-7343-40_Kemet-Y_Pad2.25x2.55mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7343-43_Kemet-X +Capacitor_Tantalum_SMD:CP_EIA-7343-43_Kemet-X_Pad2.25x2.55mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7360-38_Kemet-E +Capacitor_Tantalum_SMD:CP_EIA-7360-38_Kemet-E_Pad2.25x4.25mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7361-38_AVX-V +Capacitor_Tantalum_SMD:CP_EIA-7361-38_AVX-V_Pad2.18x3.30mm_HandSolder +Capacitor_Tantalum_SMD:CP_EIA-7361-438_AVX-U +Capacitor_Tantalum_SMD:CP_EIA-7361-438_AVX-U_Pad2.18x3.30mm_HandSolder +Capacitor_THT:CP_Axial_L10.0mm_D4.5mm_P15.00mm_Horizontal +Capacitor_THT:CP_Axial_L10.0mm_D6.0mm_P15.00mm_Horizontal +Capacitor_THT:CP_Axial_L11.0mm_D5.0mm_P18.00mm_Horizontal +Capacitor_THT:CP_Axial_L11.0mm_D6.0mm_P18.00mm_Horizontal +Capacitor_THT:CP_Axial_L11.0mm_D8.0mm_P15.00mm_Horizontal +Capacitor_THT:CP_Axial_L18.0mm_D10.0mm_P25.00mm_Horizontal +Capacitor_THT:CP_Axial_L18.0mm_D6.5mm_P25.00mm_Horizontal +Capacitor_THT:CP_Axial_L18.0mm_D8.0mm_P25.00mm_Horizontal +Capacitor_THT:CP_Axial_L20.0mm_D10.0mm_P26.00mm_Horizontal +Capacitor_THT:CP_Axial_L20.0mm_D13.0mm_P26.00mm_Horizontal +Capacitor_THT:CP_Axial_L21.0mm_D8.0mm_P28.00mm_Horizontal +Capacitor_THT:CP_Axial_L25.0mm_D10.0mm_P30.00mm_Horizontal +Capacitor_THT:CP_Axial_L26.5mm_D20.0mm_P33.00mm_Horizontal +Capacitor_THT:CP_Axial_L29.0mm_D10.0mm_P35.00mm_Horizontal +Capacitor_THT:CP_Axial_L29.0mm_D13.0mm_P35.00mm_Horizontal +Capacitor_THT:CP_Axial_L29.0mm_D16.0mm_P35.00mm_Horizontal +Capacitor_THT:CP_Axial_L29.0mm_D20.0mm_P35.00mm_Horizontal +Capacitor_THT:CP_Axial_L30.0mm_D10.0mm_P35.00mm_Horizontal +Capacitor_THT:CP_Axial_L30.0mm_D12.5mm_P35.00mm_Horizontal +Capacitor_THT:CP_Axial_L30.0mm_D15.0mm_P35.00mm_Horizontal +Capacitor_THT:CP_Axial_L30.0mm_D18.0mm_P35.00mm_Horizontal +Capacitor_THT:CP_Axial_L34.5mm_D20.0mm_P41.00mm_Horizontal +Capacitor_THT:CP_Axial_L37.0mm_D13.0mm_P43.00mm_Horizontal +Capacitor_THT:CP_Axial_L37.0mm_D16.0mm_P43.00mm_Horizontal +Capacitor_THT:CP_Axial_L37.0mm_D20.0mm_P43.00mm_Horizontal +Capacitor_THT:CP_Axial_L38.0mm_D18.0mm_P44.00mm_Horizontal +Capacitor_THT:CP_Axial_L38.0mm_D21.0mm_P44.00mm_Horizontal +Capacitor_THT:CP_Axial_L40.0mm_D16.0mm_P48.00mm_Horizontal +Capacitor_THT:CP_Axial_L42.0mm_D23.0mm_P45.00mm_Horizontal +Capacitor_THT:CP_Axial_L42.0mm_D26.0mm_P45.00mm_Horizontal +Capacitor_THT:CP_Axial_L42.0mm_D29.0mm_P45.00mm_Horizontal +Capacitor_THT:CP_Axial_L42.0mm_D32.0mm_P45.00mm_Horizontal +Capacitor_THT:CP_Axial_L42.0mm_D35.0mm_P45.00mm_Horizontal +Capacitor_THT:CP_Axial_L42.5mm_D20.0mm_P49.00mm_Horizontal +Capacitor_THT:CP_Axial_L46.0mm_D20.0mm_P52.00mm_Horizontal +Capacitor_THT:CP_Axial_L55.0mm_D23.0mm_P60.00mm_Horizontal +Capacitor_THT:CP_Axial_L55.0mm_D26.0mm_P60.00mm_Horizontal +Capacitor_THT:CP_Axial_L55.0mm_D29.0mm_P60.00mm_Horizontal +Capacitor_THT:CP_Axial_L55.0mm_D32.0mm_P60.00mm_Horizontal +Capacitor_THT:CP_Axial_L55.0mm_D35.0mm_P60.00mm_Horizontal +Capacitor_THT:CP_Axial_L67.0mm_D23.0mm_P75.00mm_Horizontal +Capacitor_THT:CP_Axial_L67.0mm_D26.0mm_P75.00mm_Horizontal +Capacitor_THT:CP_Axial_L67.0mm_D29.0mm_P75.00mm_Horizontal +Capacitor_THT:CP_Axial_L67.0mm_D32.0mm_P75.00mm_Horizontal +Capacitor_THT:CP_Axial_L67.0mm_D35.0mm_P75.00mm_Horizontal +Capacitor_THT:CP_Axial_L80.0mm_D23.0mm_P85.00mm_Horizontal +Capacitor_THT:CP_Axial_L80.0mm_D26.0mm_P85.00mm_Horizontal +Capacitor_THT:CP_Axial_L80.0mm_D29.0mm_P85.00mm_Horizontal +Capacitor_THT:CP_Axial_L80.0mm_D32.0mm_P85.00mm_Horizontal +Capacitor_THT:CP_Axial_L80.0mm_D35.0mm_P85.00mm_Horizontal +Capacitor_THT:CP_Axial_L93.0mm_D23.0mm_P100.00mm_Horizontal +Capacitor_THT:CP_Axial_L93.0mm_D26.0mm_P100.00mm_Horizontal +Capacitor_THT:CP_Axial_L93.0mm_D29.0mm_P100.00mm_Horizontal +Capacitor_THT:CP_Axial_L93.0mm_D32.0mm_P100.00mm_Horizontal +Capacitor_THT:CP_Axial_L93.0mm_D35.0mm_P100.00mm_Horizontal +Capacitor_THT:CP_Radial_D10.0mm_P2.50mm +Capacitor_THT:CP_Radial_D10.0mm_P2.50mm_P5.00mm +Capacitor_THT:CP_Radial_D10.0mm_P3.50mm +Capacitor_THT:CP_Radial_D10.0mm_P3.80mm +Capacitor_THT:CP_Radial_D10.0mm_P5.00mm +Capacitor_THT:CP_Radial_D10.0mm_P5.00mm_P7.50mm +Capacitor_THT:CP_Radial_D10.0mm_P7.50mm +Capacitor_THT:CP_Radial_D12.5mm_P2.50mm +Capacitor_THT:CP_Radial_D12.5mm_P5.00mm +Capacitor_THT:CP_Radial_D12.5mm_P7.50mm +Capacitor_THT:CP_Radial_D13.0mm_P2.50mm +Capacitor_THT:CP_Radial_D13.0mm_P5.00mm +Capacitor_THT:CP_Radial_D13.0mm_P7.50mm +Capacitor_THT:CP_Radial_D14.0mm_P5.00mm +Capacitor_THT:CP_Radial_D14.0mm_P7.50mm +Capacitor_THT:CP_Radial_D16.0mm_P7.50mm +Capacitor_THT:CP_Radial_D17.0mm_P7.50mm +Capacitor_THT:CP_Radial_D18.0mm_P7.50mm +Capacitor_THT:CP_Radial_D22.0mm_P10.00mm_3pin_SnapIn +Capacitor_THT:CP_Radial_D22.0mm_P10.00mm_SnapIn +Capacitor_THT:CP_Radial_D24.0mm_P10.00mm_3pin_SnapIn +Capacitor_THT:CP_Radial_D24.0mm_P10.00mm_SnapIn +Capacitor_THT:CP_Radial_D25.0mm_P10.00mm_3pin_SnapIn +Capacitor_THT:CP_Radial_D25.0mm_P10.00mm_SnapIn +Capacitor_THT:CP_Radial_D26.0mm_P10.00mm_3pin_SnapIn +Capacitor_THT:CP_Radial_D26.0mm_P10.00mm_SnapIn +Capacitor_THT:CP_Radial_D30.0mm_P10.00mm_3pin_SnapIn +Capacitor_THT:CP_Radial_D30.0mm_P10.00mm_SnapIn +Capacitor_THT:CP_Radial_D35.0mm_P10.00mm_3pin_SnapIn +Capacitor_THT:CP_Radial_D35.0mm_P10.00mm_SnapIn +Capacitor_THT:CP_Radial_D4.0mm_P1.50mm +Capacitor_THT:CP_Radial_D4.0mm_P2.00mm +Capacitor_THT:CP_Radial_D40.0mm_P10.00mm_3pin_SnapIn +Capacitor_THT:CP_Radial_D40.0mm_P10.00mm_SnapIn +Capacitor_THT:CP_Radial_D5.0mm_P2.00mm +Capacitor_THT:CP_Radial_D5.0mm_P2.50mm +Capacitor_THT:CP_Radial_D6.3mm_P2.50mm +Capacitor_THT:CP_Radial_D7.5mm_P2.50mm +Capacitor_THT:CP_Radial_D8.0mm_P2.50mm +Capacitor_THT:CP_Radial_D8.0mm_P3.50mm +Capacitor_THT:CP_Radial_D8.0mm_P3.80mm +Capacitor_THT:CP_Radial_D8.0mm_P5.00mm +Capacitor_THT:CP_Radial_Tantal_D10.5mm_P2.50mm +Capacitor_THT:CP_Radial_Tantal_D10.5mm_P5.00mm +Capacitor_THT:CP_Radial_Tantal_D4.5mm_P2.50mm +Capacitor_THT:CP_Radial_Tantal_D4.5mm_P5.00mm +Capacitor_THT:CP_Radial_Tantal_D5.0mm_P2.50mm +Capacitor_THT:CP_Radial_Tantal_D5.0mm_P5.00mm +Capacitor_THT:CP_Radial_Tantal_D5.5mm_P2.50mm +Capacitor_THT:CP_Radial_Tantal_D5.5mm_P5.00mm +Capacitor_THT:CP_Radial_Tantal_D6.0mm_P2.50mm +Capacitor_THT:CP_Radial_Tantal_D6.0mm_P5.00mm +Capacitor_THT:CP_Radial_Tantal_D7.0mm_P2.50mm +Capacitor_THT:CP_Radial_Tantal_D7.0mm_P5.00mm +Capacitor_THT:CP_Radial_Tantal_D8.0mm_P2.50mm +Capacitor_THT:CP_Radial_Tantal_D8.0mm_P5.00mm +Capacitor_THT:CP_Radial_Tantal_D9.0mm_P2.50mm +Capacitor_THT:CP_Radial_Tantal_D9.0mm_P5.00mm +Capacitor_THT:C_Axial_L12.0mm_D10.5mm_P15.00mm_Horizontal +Capacitor_THT:C_Axial_L12.0mm_D10.5mm_P20.00mm_Horizontal +Capacitor_THT:C_Axial_L12.0mm_D6.5mm_P15.00mm_Horizontal +Capacitor_THT:C_Axial_L12.0mm_D6.5mm_P20.00mm_Horizontal +Capacitor_THT:C_Axial_L12.0mm_D7.5mm_P15.00mm_Horizontal +Capacitor_THT:C_Axial_L12.0mm_D7.5mm_P20.00mm_Horizontal +Capacitor_THT:C_Axial_L12.0mm_D8.5mm_P15.00mm_Horizontal +Capacitor_THT:C_Axial_L12.0mm_D8.5mm_P20.00mm_Horizontal +Capacitor_THT:C_Axial_L12.0mm_D9.5mm_P15.00mm_Horizontal +Capacitor_THT:C_Axial_L12.0mm_D9.5mm_P20.00mm_Horizontal +Capacitor_THT:C_Axial_L17.0mm_D6.5mm_P20.00mm_Horizontal +Capacitor_THT:C_Axial_L17.0mm_D6.5mm_P25.00mm_Horizontal +Capacitor_THT:C_Axial_L17.0mm_D7.0mm_P20.00mm_Horizontal +Capacitor_THT:C_Axial_L17.0mm_D7.0mm_P25.00mm_Horizontal +Capacitor_THT:C_Axial_L19.0mm_D7.5mm_P25.00mm_Horizontal +Capacitor_THT:C_Axial_L19.0mm_D8.0mm_P25.00mm_Horizontal +Capacitor_THT:C_Axial_L19.0mm_D9.0mm_P25.00mm_Horizontal +Capacitor_THT:C_Axial_L19.0mm_D9.5mm_P25.00mm_Horizontal +Capacitor_THT:C_Axial_L22.0mm_D10.5mm_P27.50mm_Horizontal +Capacitor_THT:C_Axial_L22.0mm_D9.5mm_P27.50mm_Horizontal +Capacitor_THT:C_Axial_L3.8mm_D2.6mm_P10.00mm_Horizontal +Capacitor_THT:C_Axial_L3.8mm_D2.6mm_P12.50mm_Horizontal +Capacitor_THT:C_Axial_L3.8mm_D2.6mm_P15.00mm_Horizontal +Capacitor_THT:C_Axial_L3.8mm_D2.6mm_P7.50mm_Horizontal +Capacitor_THT:C_Axial_L5.1mm_D3.1mm_P10.00mm_Horizontal +Capacitor_THT:C_Axial_L5.1mm_D3.1mm_P12.50mm_Horizontal +Capacitor_THT:C_Axial_L5.1mm_D3.1mm_P15.00mm_Horizontal +Capacitor_THT:C_Axial_L5.1mm_D3.1mm_P7.50mm_Horizontal +Capacitor_THT:C_Disc_D10.0mm_W2.5mm_P5.00mm +Capacitor_THT:C_Disc_D10.5mm_W5.0mm_P10.00mm +Capacitor_THT:C_Disc_D10.5mm_W5.0mm_P5.00mm +Capacitor_THT:C_Disc_D10.5mm_W5.0mm_P7.50mm +Capacitor_THT:C_Disc_D11.0mm_W5.0mm_P10.00mm +Capacitor_THT:C_Disc_D11.0mm_W5.0mm_P5.00mm +Capacitor_THT:C_Disc_D11.0mm_W5.0mm_P7.50mm +Capacitor_THT:C_Disc_D12.0mm_W4.4mm_P7.75mm +Capacitor_THT:C_Disc_D12.5mm_W5.0mm_P10.00mm +Capacitor_THT:C_Disc_D12.5mm_W5.0mm_P7.50mm +Capacitor_THT:C_Disc_D14.5mm_W5.0mm_P10.00mm +Capacitor_THT:C_Disc_D14.5mm_W5.0mm_P7.50mm +Capacitor_THT:C_Disc_D16.0mm_W5.0mm_P10.00mm +Capacitor_THT:C_Disc_D16.0mm_W5.0mm_P7.50mm +Capacitor_THT:C_Disc_D3.0mm_W1.6mm_P2.50mm +Capacitor_THT:C_Disc_D3.0mm_W2.0mm_P2.50mm +Capacitor_THT:C_Disc_D3.4mm_W2.1mm_P2.50mm +Capacitor_THT:C_Disc_D3.8mm_W2.6mm_P2.50mm +Capacitor_THT:C_Disc_D4.3mm_W1.9mm_P5.00mm +Capacitor_THT:C_Disc_D4.7mm_W2.5mm_P5.00mm +Capacitor_THT:C_Disc_D5.0mm_W2.5mm_P2.50mm +Capacitor_THT:C_Disc_D5.0mm_W2.5mm_P5.00mm +Capacitor_THT:C_Disc_D5.1mm_W3.2mm_P5.00mm +Capacitor_THT:C_Disc_D6.0mm_W2.5mm_P5.00mm +Capacitor_THT:C_Disc_D6.0mm_W4.4mm_P5.00mm +Capacitor_THT:C_Disc_D7.0mm_W2.5mm_P5.00mm +Capacitor_THT:C_Disc_D7.5mm_W2.5mm_P5.00mm +Capacitor_THT:C_Disc_D7.5mm_W4.4mm_P5.00mm +Capacitor_THT:C_Disc_D7.5mm_W5.0mm_P10.00mm +Capacitor_THT:C_Disc_D7.5mm_W5.0mm_P5.00mm +Capacitor_THT:C_Disc_D7.5mm_W5.0mm_P7.50mm +Capacitor_THT:C_Disc_D8.0mm_W2.5mm_P5.00mm +Capacitor_THT:C_Disc_D8.0mm_W5.0mm_P10.00mm +Capacitor_THT:C_Disc_D8.0mm_W5.0mm_P5.00mm +Capacitor_THT:C_Disc_D8.0mm_W5.0mm_P7.50mm +Capacitor_THT:C_Disc_D9.0mm_W2.5mm_P5.00mm +Capacitor_THT:C_Disc_D9.0mm_W5.0mm_P10.00mm +Capacitor_THT:C_Disc_D9.0mm_W5.0mm_P5.00mm +Capacitor_THT:C_Disc_D9.0mm_W5.0mm_P7.50mm +Capacitor_THT:C_Radial_D10.0mm_H12.5mm_P5.00mm +Capacitor_THT:C_Radial_D10.0mm_H16.0mm_P5.00mm +Capacitor_THT:C_Radial_D10.0mm_H20.0mm_P5.00mm +Capacitor_THT:C_Radial_D12.5mm_H20.0mm_P5.00mm +Capacitor_THT:C_Radial_D12.5mm_H25.0mm_P5.00mm +Capacitor_THT:C_Radial_D16.0mm_H25.0mm_P7.50mm +Capacitor_THT:C_Radial_D16.0mm_H31.5mm_P7.50mm +Capacitor_THT:C_Radial_D18.0mm_H35.5mm_P7.50mm +Capacitor_THT:C_Radial_D4.0mm_H5.0mm_P1.50mm +Capacitor_THT:C_Radial_D4.0mm_H7.0mm_P1.50mm +Capacitor_THT:C_Radial_D5.0mm_H11.0mm_P2.00mm +Capacitor_THT:C_Radial_D5.0mm_H5.0mm_P2.00mm +Capacitor_THT:C_Radial_D5.0mm_H7.0mm_P2.00mm +Capacitor_THT:C_Radial_D6.3mm_H11.0mm_P2.50mm +Capacitor_THT:C_Radial_D6.3mm_H5.0mm_P2.50mm +Capacitor_THT:C_Radial_D6.3mm_H7.0mm_P2.50mm +Capacitor_THT:C_Radial_D8.0mm_H11.5mm_P3.50mm +Capacitor_THT:C_Radial_D8.0mm_H7.0mm_P3.50mm +Capacitor_THT:C_Rect_L10.0mm_W2.5mm_P7.50mm_MKS4 +Capacitor_THT:C_Rect_L10.0mm_W3.0mm_P7.50mm_FKS3_FKP3 +Capacitor_THT:C_Rect_L10.0mm_W3.0mm_P7.50mm_MKS4 +Capacitor_THT:C_Rect_L10.0mm_W4.0mm_P7.50mm_FKS3_FKP3 +Capacitor_THT:C_Rect_L10.0mm_W4.0mm_P7.50mm_MKS4 +Capacitor_THT:C_Rect_L10.0mm_W5.0mm_P5.00mm_P7.50mm +Capacitor_THT:C_Rect_L10.3mm_W4.5mm_P7.50mm_MKS4 +Capacitor_THT:C_Rect_L10.3mm_W5.0mm_P7.50mm_MKS4 +Capacitor_THT:C_Rect_L10.3mm_W5.7mm_P7.50mm_MKS4 +Capacitor_THT:C_Rect_L10.3mm_W7.2mm_P7.50mm_MKS4 +Capacitor_THT:C_Rect_L11.0mm_W2.8mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.0mm_W3.4mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.0mm_W3.5mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.0mm_W4.2mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.0mm_W4.3mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.0mm_W5.1mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.0mm_W5.3mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.0mm_W6.3mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.0mm_W6.4mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.0mm_W7.3mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.0mm_W8.8mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W2.0mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W2.6mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W2.8mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W3.2mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W3.5mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W3.6mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W4.0mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W4.3mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W4.5mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W5.0mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W5.1mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W5.2mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W5.6mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W6.4mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W6.6mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W6.9mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W7.3mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W7.5mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W7.8mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W8.0mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W8.8mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W9.5mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L11.5mm_W9.8mm_P10.00mm_MKT +Capacitor_THT:C_Rect_L13.0mm_W3.0mm_P10.00mm_FKS3_FKP3_MKS4 +Capacitor_THT:C_Rect_L13.0mm_W4.0mm_P10.00mm_FKS3_FKP3_MKS4 +Capacitor_THT:C_Rect_L13.0mm_W5.0mm_P10.00mm_FKS3_FKP3_MKS4 +Capacitor_THT:C_Rect_L13.0mm_W6.0mm_P10.00mm_FKS3_FKP3_MKS4 +Capacitor_THT:C_Rect_L13.0mm_W6.5mm_P7.50mm_P10.00mm +Capacitor_THT:C_Rect_L13.0mm_W8.0mm_P10.00mm_FKS3_FKP3_MKS4 +Capacitor_THT:C_Rect_L13.5mm_W4.0mm_P10.00mm_FKS3_FKP3_MKS4 +Capacitor_THT:C_Rect_L13.5mm_W5.0mm_P10.00mm_FKS3_FKP3_MKS4 +Capacitor_THT:C_Rect_L16.5mm_W10.7mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W10.9mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W11.2mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W11.8mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W13.5mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W13.7mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W13.9mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W4.7mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W4.9mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W5.0mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W6.0mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W7.0mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W7.3mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W8.7mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W8.9mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W9.0mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L16.5mm_W9.2mm_P15.00mm_MKT +Capacitor_THT:C_Rect_L18.0mm_W11.0mm_P15.00mm_FKS3_FKP3 +Capacitor_THT:C_Rect_L18.0mm_W5.0mm_P15.00mm_FKS3_FKP3 +Capacitor_THT:C_Rect_L18.0mm_W6.0mm_P15.00mm_FKS3_FKP3 +Capacitor_THT:C_Rect_L18.0mm_W7.0mm_P15.00mm_FKS3_FKP3 +Capacitor_THT:C_Rect_L18.0mm_W8.0mm_P15.00mm_FKS3_FKP3 +Capacitor_THT:C_Rect_L18.0mm_W9.0mm_P15.00mm_FKS3_FKP3 +Capacitor_THT:C_Rect_L19.0mm_W11.0mm_P15.00mm_MKS4 +Capacitor_THT:C_Rect_L19.0mm_W5.0mm_P15.00mm_MKS4 +Capacitor_THT:C_Rect_L19.0mm_W6.0mm_P15.00mm_MKS4 +Capacitor_THT:C_Rect_L19.0mm_W7.0mm_P15.00mm_MKS4 +Capacitor_THT:C_Rect_L19.0mm_W8.0mm_P15.00mm_MKS4 +Capacitor_THT:C_Rect_L19.0mm_W9.0mm_P15.00mm_MKS4 +Capacitor_THT:C_Rect_L24.0mm_W10.1mm_P22.50mm_MKT +Capacitor_THT:C_Rect_L24.0mm_W10.3mm_P22.50mm_MKT +Capacitor_THT:C_Rect_L24.0mm_W10.9mm_P22.50mm_MKT +Capacitor_THT:C_Rect_L24.0mm_W12.2mm_P22.50mm_MKT +Capacitor_THT:C_Rect_L24.0mm_W12.6mm_P22.50mm_MKT +Capacitor_THT:C_Rect_L24.0mm_W12.8mm_P22.50mm_MKT +Capacitor_THT:C_Rect_L24.0mm_W7.0mm_P22.50mm_MKT +Capacitor_THT:C_Rect_L24.0mm_W8.3mm_P22.50mm_MKT +Capacitor_THT:C_Rect_L24.0mm_W8.6mm_P22.50mm_MKT +Capacitor_THT:C_Rect_L26.5mm_W10.5mm_P22.50mm_MKS4 +Capacitor_THT:C_Rect_L26.5mm_W11.5mm_P22.50mm_MKS4 +Capacitor_THT:C_Rect_L26.5mm_W5.0mm_P22.50mm_MKS4 +Capacitor_THT:C_Rect_L26.5mm_W6.0mm_P22.50mm_MKS4 +Capacitor_THT:C_Rect_L26.5mm_W7.0mm_P22.50mm_MKS4 +Capacitor_THT:C_Rect_L26.5mm_W8.5mm_P22.50mm_MKS4 +Capacitor_THT:C_Rect_L27.0mm_W11.0mm_P22.00mm +Capacitor_THT:C_Rect_L27.0mm_W9.0mm_P22.00mm +Capacitor_THT:C_Rect_L27.0mm_W9.0mm_P23.00mm +Capacitor_THT:C_Rect_L28.0mm_W10.0mm_P22.50mm_MKS4 +Capacitor_THT:C_Rect_L28.0mm_W12.0mm_P22.50mm_MKS4 +Capacitor_THT:C_Rect_L28.0mm_W8.0mm_P22.50mm_MKS4 +Capacitor_THT:C_Rect_L29.0mm_W11.0mm_P27.50mm_MKT +Capacitor_THT:C_Rect_L29.0mm_W11.9mm_P27.50mm_MKT +Capacitor_THT:C_Rect_L29.0mm_W12.2mm_P27.50mm_MKT +Capacitor_THT:C_Rect_L29.0mm_W13.0mm_P27.50mm_MKT +Capacitor_THT:C_Rect_L29.0mm_W13.8mm_P27.50mm_MKT +Capacitor_THT:C_Rect_L29.0mm_W14.2mm_P27.50mm_MKT +Capacitor_THT:C_Rect_L29.0mm_W16.0mm_P27.50mm_MKT +Capacitor_THT:C_Rect_L29.0mm_W7.6mm_P27.50mm_MKT +Capacitor_THT:C_Rect_L29.0mm_W7.8mm_P27.50mm_MKT +Capacitor_THT:C_Rect_L29.0mm_W7.9mm_P27.50mm_MKT +Capacitor_THT:C_Rect_L29.0mm_W9.1mm_P27.50mm_MKT +Capacitor_THT:C_Rect_L29.0mm_W9.6mm_P27.50mm_MKT +Capacitor_THT:C_Rect_L31.5mm_W11.0mm_P27.50mm_MKS4 +Capacitor_THT:C_Rect_L31.5mm_W13.0mm_P27.50mm_MKS4 +Capacitor_THT:C_Rect_L31.5mm_W15.0mm_P27.50mm_MKS4 +Capacitor_THT:C_Rect_L31.5mm_W17.0mm_P27.50mm_MKS4 +Capacitor_THT:C_Rect_L31.5mm_W20.0mm_P27.50mm_MKS4 +Capacitor_THT:C_Rect_L31.5mm_W9.0mm_P27.50mm_MKS4 +Capacitor_THT:C_Rect_L32.0mm_W15.0mm_P27.00mm +Capacitor_THT:C_Rect_L33.0mm_W13.0mm_P27.50mm_MKS4 +Capacitor_THT:C_Rect_L33.0mm_W15.0mm_P27.50mm_MKS4 +Capacitor_THT:C_Rect_L33.0mm_W20.0mm_P27.50mm_MKS4 +Capacitor_THT:C_Rect_L4.0mm_W2.5mm_P2.50mm +Capacitor_THT:C_Rect_L4.6mm_W2.0mm_P2.50mm_MKS02_FKP02 +Capacitor_THT:C_Rect_L4.6mm_W3.0mm_P2.50mm_MKS02_FKP02 +Capacitor_THT:C_Rect_L4.6mm_W3.8mm_P2.50mm_MKS02_FKP02 +Capacitor_THT:C_Rect_L4.6mm_W4.6mm_P2.50mm_MKS02_FKP02 +Capacitor_THT:C_Rect_L4.6mm_W5.5mm_P2.50mm_MKS02_FKP02 +Capacitor_THT:C_Rect_L41.5mm_W11.0mm_P37.50mm_MKS4 +Capacitor_THT:C_Rect_L41.5mm_W13.0mm_P37.50mm_MKS4 +Capacitor_THT:C_Rect_L41.5mm_W15.0mm_P37.50mm_MKS4 +Capacitor_THT:C_Rect_L41.5mm_W17.0mm_P37.50mm_MKS4 +Capacitor_THT:C_Rect_L41.5mm_W19.0mm_P37.50mm_MKS4 +Capacitor_THT:C_Rect_L41.5mm_W20.0mm_P37.50mm_MKS4 +Capacitor_THT:C_Rect_L41.5mm_W24.0mm_P37.50mm_MKS4 +Capacitor_THT:C_Rect_L41.5mm_W31.0mm_P37.50mm_MKS4 +Capacitor_THT:C_Rect_L41.5mm_W35.0mm_P37.50mm_MKS4 +Capacitor_THT:C_Rect_L41.5mm_W40.0mm_P37.50mm_MKS4 +Capacitor_THT:C_Rect_L41.5mm_W9.0mm_P37.50mm_MKS4 +Capacitor_THT:C_Rect_L7.0mm_W2.0mm_P5.00mm +Capacitor_THT:C_Rect_L7.0mm_W2.5mm_P5.00mm +Capacitor_THT:C_Rect_L7.0mm_W3.5mm_P2.50mm_P5.00mm +Capacitor_THT:C_Rect_L7.0mm_W3.5mm_P5.00mm +Capacitor_THT:C_Rect_L7.0mm_W4.5mm_P5.00mm +Capacitor_THT:C_Rect_L7.0mm_W6.0mm_P5.00mm +Capacitor_THT:C_Rect_L7.0mm_W6.5mm_P5.00mm +Capacitor_THT:C_Rect_L7.2mm_W11.0mm_P5.00mm_FKS2_FKP2_MKS2_MKP2 +Capacitor_THT:C_Rect_L7.2mm_W2.5mm_P5.00mm_FKS2_FKP2_MKS2_MKP2 +Capacitor_THT:C_Rect_L7.2mm_W3.0mm_P5.00mm_FKS2_FKP2_MKS2_MKP2 +Capacitor_THT:C_Rect_L7.2mm_W3.5mm_P5.00mm_FKS2_FKP2_MKS2_MKP2 +Capacitor_THT:C_Rect_L7.2mm_W4.5mm_P5.00mm_FKS2_FKP2_MKS2_MKP2 +Capacitor_THT:C_Rect_L7.2mm_W5.5mm_P5.00mm_FKS2_FKP2_MKS2_MKP2 +Capacitor_THT:C_Rect_L7.2mm_W7.2mm_P5.00mm_FKS2_FKP2_MKS2_MKP2 +Capacitor_THT:C_Rect_L7.2mm_W8.5mm_P5.00mm_FKP2_FKP2_MKS2_MKP2 +Capacitor_THT:C_Rect_L7.5mm_W6.5mm_P5.00mm +Capacitor_THT:C_Rect_L9.0mm_W2.5mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W2.6mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W2.7mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W3.2mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W3.3mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W3.4mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W3.6mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W3.8mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W3.9mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W4.0mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W4.2mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W4.9mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W5.1mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W5.7mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W6.4mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W6.7mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W7.7mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W8.5mm_P7.50mm_MKT +Capacitor_THT:C_Rect_L9.0mm_W9.5mm_P7.50mm_MKT +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_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 +Connector:CalTest_CT3151 +Connector:Connector_SFP_and_Cage +Connector:CUI_PD-30 +Connector:CUI_PD-30S +Connector:CUI_PD-30S_CircularHoles +Connector:DTF13-12Px +Connector:FanPinHeader_1x03_P2.54mm_Vertical +Connector:FanPinHeader_1x04_P2.54mm_Vertical +Connector:GB042-34S-H10 +Connector:IHI_B6A-PCB-45_Vertical +Connector:Joint-Tech_C5080WR-04P_1x04_P5.08mm_Vertical +Connector:JWT_A3963_1x02_P3.96mm_Vertical +Connector:NS-Tech_Grove_1x04_P2mm_Vertical +Connector:OCN_OK-01GM030-04_2x15_P0.4mm_Vertical +Connector:SpringContact_Harwin_S1941-46R +Connector:Tag-Connect_TC2030-IDC-FP_2x03_P1.27mm_Vertical +Connector:Tag-Connect_TC2030-IDC-NL_2x03_P1.27mm_Vertical +Connector:Tag-Connect_TC2050-IDC-FP_2x05_P1.27mm_Vertical +Connector:Tag-Connect_TC2050-IDC-NL_2x05_P1.27mm_Vertical +Connector:Tag-Connect_TC2050-IDC-NL_2x05_P1.27mm_Vertical_with_bottom_clip +Connector:Tag-Connect_TC2070-IDC-FP_2x07_P1.27mm_Vertical +Connector_AMASS:AMASS_MR30PW-FB_1x03_P3.50mm_Horizontal +Connector_AMASS:AMASS_MR30PW-M_1x03_P3.50mm_Horizontal +Connector_AMASS:AMASS_XT30PW-F_1x02_P2.50mm_Horizontal +Connector_AMASS:AMASS_XT30PW-M_1x02_P2.50mm_Horizontal +Connector_AMASS:AMASS_XT30U-F_1x02_P5.0mm_Vertical +Connector_AMASS:AMASS_XT30U-M_1x02_P5.0mm_Vertical +Connector_AMASS:AMASS_XT30UPB-F_1x02_P5.0mm_Vertical +Connector_AMASS:AMASS_XT30UPB-M_1x02_P5.0mm_Vertical +Connector_AMASS:AMASS_XT60-F_1x02_P7.20mm_Vertical +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 +Connector_Audio:Jack_3.5mm_CUI_SJ1-3533NG_Horizontal +Connector_Audio:Jack_3.5mm_CUI_SJ1-3533NG_Horizontal_CircularHoles +Connector_Audio:Jack_3.5mm_CUI_SJ1-3535NG_Horizontal +Connector_Audio:Jack_3.5mm_CUI_SJ1-3535NG_Horizontal_CircularHoles +Connector_Audio:Jack_3.5mm_CUI_SJ2-3593D-SMT_Horizontal +Connector_Audio:Jack_3.5mm_KoreanHropartsElec_PJ-320D-4A_Horizontal +Connector_Audio:Jack_3.5mm_Ledino_KB3SPRS_Horizontal +Connector_Audio:Jack_3.5mm_Lumberg_1503_02_Horizontal +Connector_Audio:Jack_3.5mm_Lumberg_1503_03_Horizontal +Connector_Audio:Jack_3.5mm_Lumberg_1503_07_Horizontal +Connector_Audio:Jack_3.5mm_PJ31060-I_Horizontal +Connector_Audio:Jack_3.5mm_PJ311_Horizontal +Connector_Audio:Jack_3.5mm_PJ320D_Horizontal +Connector_Audio:Jack_3.5mm_PJ320E_Horizontal +Connector_Audio:Jack_3.5mm_QingPu_WQP-PJ398SM_Vertical_CircularHoles +Connector_Audio:Jack_3.5mm_Switronic_ST-005-G_horizontal +Connector_Audio:Jack_3.5mm_Technik_TWP-3002_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NJ2FD-V_Vertical +Connector_Audio:Jack_6.35mm_Neutrik_NJ3FD-V_Vertical +Connector_Audio:Jack_6.35mm_Neutrik_NJ5FD-V_Vertical +Connector_Audio:Jack_6.35mm_Neutrik_NJ6FD-V_Vertical +Connector_Audio:Jack_6.35mm_Neutrik_NJ6TB-V_Vertical +Connector_Audio:Jack_6.35mm_Neutrik_NMJ4HCD2_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NMJ4HFD2_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NMJ4HFD3_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NMJ4HHD2_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NMJ6HCD2_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NMJ6HCD3_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NMJ6HFD2-AU_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NMJ6HFD2_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NMJ6HFD3_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NMJ6HFD4_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NMJ6HHD2_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ3HF-1_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ4HF-1_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ4HF_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ4HH-1_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ4HH_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ6HF-1-AU_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ6HF-1_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ6HF-AU_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ6HF_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ6HH-1_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ6HH-AU_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ6HH_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ6HM-1-AU_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ6HM-1-PRE_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NRJ6HM-1_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NSJ12HC_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NSJ12HF-1_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NSJ12HH-1_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NSJ12HL_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NSJ8HC_Horizontal +Connector_Audio:Jack_6.35mm_Neutrik_NSJ8HL_Horizontal +Connector_Audio:Jack_speakON-6.35mm_Neutrik_NLJ2MDXX-H_Horizontal +Connector_Audio:Jack_speakON-6.35mm_Neutrik_NLJ2MDXX-V_Vertical +Connector_Audio:Jack_speakON_Neutrik_NL2MDXX-H-3_Horizontal +Connector_Audio:Jack_speakON_Neutrik_NL2MDXX-V_Vertical +Connector_Audio:Jack_speakON_Neutrik_NL4MDXX-H-2_Horizontal +Connector_Audio:Jack_speakON_Neutrik_NL4MDXX-H-3_Horizontal +Connector_Audio:Jack_speakON_Neutrik_NL4MDXX-V-2_Vertical +Connector_Audio:Jack_speakON_Neutrik_NL4MDXX-V-3_Vertical +Connector_Audio:Jack_speakON_Neutrik_NL4MDXX-V_Vertical +Connector_Audio:Jack_speakON_Neutrik_NL8MDXX-V-3_Vertical +Connector_Audio:Jack_speakON_Neutrik_NL8MDXX-V_Vertical +Connector_Audio:Jack_speakON_Neutrik_NLT4MD-V_Vertical +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ10FI-H-0_Horizontal +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ10FI-H_Horizontal +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ10FI-V-0_Vertical +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ10FI-V_Vertical +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ5FI-H-0_Horizontal +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ5FI-H_Horizontal +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ5FI-V-0_Vertical +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ5FI-V_Vertical +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ6FA-H-0_Horizontal +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ6FA-H-DA_Horizontal +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ6FA-H_Horizontal +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ6FA-V-0_Vertical +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ6FA-V-DA_Vertical +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ6FA-V_Vertical +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ6FI-H-0_Horizontal +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ6FI-H_Horizontal +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ6FI-V-0_Vertical +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ6FI-V_Vertical +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ9FI-H-0_Horizontal +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ9FI-H_Horizontal +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ9FI-V-0_Vertical +Connector_Audio:Jack_XLR-6.35mm_Neutrik_NCJ9FI-V_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAAH-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAAH1-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAAH1-DA_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAAH1_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAAH2-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAAH2_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAAH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAAV-0_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAAV1-0_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAAV1-DA_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAAV1_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAAV2-0_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAAV2_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAAV_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAH-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAH1-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAH1-DA_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAH1_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAH2-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAH2-DA_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAH2_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAHL-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAHL1-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAHL1_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAHR-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAHR1-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAHR1_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAHR2-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAHR2_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FAV-0_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAV1-0_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAV1-DA_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAV1_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAV2-0_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAV2-DA_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAV2_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FAV_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FBH1-B_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FBH1-DA_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FBH1-E_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FBH1_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FBH2-B_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FBH2-DA_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FBH2-E_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FBH2_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FBHL1_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3FBV1-0_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FBV1-B_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FBV1-DA_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FBV1_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FBV2-B_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FBV2-DA_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FBV2-SW_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3FBV2_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3MAAH-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MAAH-1_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MAAH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MAAV-0_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3MAAV-1_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3MAAV_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3MAFH-PH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MAH-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MAHL_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MAHR_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MAH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MAMH-PH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MAV-0_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3MAV_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3MBH-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MBH-1_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MBH-B_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MBH-E_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MBHL-B_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MBHL_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MBHR-B_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MBHR_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MBH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC3MBV-0_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3MBV-1_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3MBV-B_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3MBV-E_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3MBV-SW_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC3MBV_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC4FAH-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC4FAH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC4FAV-0_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC4FAV_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC4FBH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC4FBV_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC4MAH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC4MAV_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC4MBH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC4MBV_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC5FAH-0_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC5FAH-DA_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC5FAH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC5FAV-DA_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC5FAV-SW_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC5FAV_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC5FBH-B_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC5FBH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC5FBV-B_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC5FBV-SW_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC5FBV_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC5MAH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC5MAV-SW_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC5MAV_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC5MBH-B_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC5MBH_Horizontal +Connector_Audio:Jack_XLR_Neutrik_NC5MBV-B_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC5MBV-SW_Vertical +Connector_Audio:Jack_XLR_Neutrik_NC5MBV_Vertical +Connector_Audio:MiniXLR-5_Switchcraft_TRAPC_Horizontal +Connector_Audio:Plug_3.5mm_CUI_SP-3541 +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 +Connector_Card:CF-Card_3M_N7E50-A516xx-30 +Connector_Card:CF-Card_3M_N7E50-E516xx-30 +Connector_Card:microSD_HC_Hirose_DM3AT-SF-PEJM5 +Connector_Card:microSD_HC_Hirose_DM3BT-DSF-PEJS +Connector_Card:microSD_HC_Hirose_DM3D-SF +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+ +Connector_Card:SD_Kyocera_145638109211859+ +Connector_Card:SD_Kyocera_145638109511859+ +Connector_Card:SD_TE_2041021 +Connector_Coaxial:BNC_Amphenol_031-5539_Vertical +Connector_Coaxial:BNC_Amphenol_031-6575_Horizontal +Connector_Coaxial:BNC_Amphenol_B6252HB-NPP3G-50_Horizontal +Connector_Coaxial:BNC_PanelMountable_Vertical +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 +Connector_Coaxial:MMCX_Molex_73415-1471_Vertical +Connector_Coaxial:SMA_Amphenol_132134-10_Vertical +Connector_Coaxial:SMA_Amphenol_132134-11_Vertical +Connector_Coaxial:SMA_Amphenol_132134-14_Vertical +Connector_Coaxial:SMA_Amphenol_132134-16_Vertical +Connector_Coaxial:SMA_Amphenol_132134_Vertical +Connector_Coaxial:SMA_Amphenol_132203-12_Horizontal +Connector_Coaxial:SMA_Amphenol_132289_EdgeMount +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-H-ST-EM1_EdgeMount +Connector_Coaxial:SMA_Wurth_60312002114503_Vertical +Connector_Coaxial:SMA_Wurth_60312102114405_Vertical +Connector_Coaxial:SMB_Jack_Vertical +Connector_Coaxial:U.FL_Hirose_U.FL-R-SMT-1_Vertical +Connector_Coaxial:U.FL_Molex_MCRF_73412-0110_Vertical +Connector_Coaxial:WR-MMCX_Wuerth_66011102111302_Horizontal +Connector_Coaxial:WR-MMCX_Wuerth_66012102111404_Vertical +Connector_DIN:DIN41612_B2_2x16_Female_Vertical_THT +Connector_DIN:DIN41612_B2_2x16_Male_Horizontal_THT +Connector_DIN:DIN41612_B2_2x8_Female_Vertical_THT +Connector_DIN:DIN41612_B2_2x8_Male_Horizontal_THT +Connector_DIN:DIN41612_B3_2x10_Female_Vertical_THT +Connector_DIN:DIN41612_B3_2x10_Male_Horizontal_THT +Connector_DIN:DIN41612_B3_2x5_Female_Vertical_THT +Connector_DIN:DIN41612_B3_2x5_Male_Horizontal_THT +Connector_DIN:DIN41612_B_1x32_Female_Vertical_THT +Connector_DIN:DIN41612_B_1x32_Male_Horizontal_THT +Connector_DIN:DIN41612_B_2x16_Female_Vertical_THT +Connector_DIN:DIN41612_B_2x16_Male_Horizontal_THT +Connector_DIN:DIN41612_B_2x32_Female_Vertical_THT +Connector_DIN:DIN41612_B_2x32_Male_Horizontal_THT +Connector_DIN:DIN41612_C2_2x16_Female_Vertical_THT +Connector_DIN:DIN41612_C2_2x16_Male_Horizontal_THT +Connector_DIN:DIN41612_C2_3x16_Female_Vertical_THT +Connector_DIN:DIN41612_C2_3x16_Male_Horizontal_THT +Connector_DIN:DIN41612_C3_2x10_Female_Vertical_THT +Connector_DIN:DIN41612_C3_2x10_Male_Horizontal_THT +Connector_DIN:DIN41612_C3_3x10_Female_Vertical_THT +Connector_DIN:DIN41612_C3_3x10_Male_Horizontal_THT +Connector_DIN:DIN41612_C_1x32_Female_Vertical_THT +Connector_DIN:DIN41612_C_1x32_Male_Horizontal_THT +Connector_DIN:DIN41612_C_2x16_Female_Vertical_THT +Connector_DIN:DIN41612_C_2x16_Male_Horizontal_THT +Connector_DIN:DIN41612_C_2x32_Female_Vertical_THT +Connector_DIN:DIN41612_C_2x32_Male_Horizontal_THT +Connector_DIN:DIN41612_C_3x16_Female_Vertical_THT +Connector_DIN:DIN41612_C_3x16_Male_Horizontal_THT +Connector_DIN:DIN41612_C_3x32_Female_Vertical_THT +Connector_DIN:DIN41612_C_3x32_Male_Horizontal_THT +Connector_DIN:DIN41612_D_2x16_Female_Vertical_THT +Connector_DIN:DIN41612_D_2x16_Male_Horizontal_THT +Connector_DIN:DIN41612_D_2x8_Female_Vertical_THT +Connector_DIN:DIN41612_D_2x8_Male_Horizontal_THT +Connector_DIN:DIN41612_E_2x16_Female_Vertical_THT +Connector_DIN:DIN41612_E_2x16_Male_Horizontal_THT +Connector_DIN:DIN41612_E_2x16_RowsAC_Female_Vertical_THT +Connector_DIN:DIN41612_E_2x16_RowsAC_Male_Horizontal_THT +Connector_DIN:DIN41612_E_3x16_Female_Vertical_THT +Connector_DIN:DIN41612_E_3x16_Male_Horizontal_THT +Connector_DIN:DIN41612_F_2x16_Female_Vertical_THT +Connector_DIN:DIN41612_F_2x16_Male_Horizontal_THT +Connector_DIN:DIN41612_F_2x16_RowsZD_Female_Vertical_THT +Connector_DIN:DIN41612_F_2x16_RowsZD_Male_Horizontal_THT +Connector_DIN:DIN41612_F_3x16_Female_Vertical_THT +Connector_DIN:DIN41612_F_3x16_Male_Horizontal_THT +Connector_DIN:DIN41612_Q2_2x16_Female_Horizontal_THT +Connector_DIN:DIN41612_Q2_2x16_Male_Vertical_THT +Connector_DIN:DIN41612_Q3_2x10_Female_Horizontal_THT +Connector_DIN:DIN41612_Q3_2x10_Male_Vertical_THT +Connector_DIN:DIN41612_Q_2x32_Female_Horizontal_THT +Connector_DIN:DIN41612_Q_2x32_Male_Vertical_THT +Connector_DIN:DIN41612_R2_2x16_Female_Horizontal_THT +Connector_DIN:DIN41612_R2_2x16_Male_Vertical_THT +Connector_DIN:DIN41612_R2_3x16_Female_Horizontal_THT +Connector_DIN:DIN41612_R2_3x16_Male_Vertical_THT +Connector_DIN:DIN41612_R3_2x10_Female_Horizontal_THT +Connector_DIN:DIN41612_R3_2x10_Male_Vertical_THT +Connector_DIN:DIN41612_R3_3x10_Female_Horizontal_THT +Connector_DIN:DIN41612_R3_3x10_Male_Vertical_THT +Connector_DIN:DIN41612_R_1x32_Female_Horizontal_THT +Connector_DIN:DIN41612_R_1x32_Male_Vertical_THT +Connector_DIN:DIN41612_R_2x16_Female_Horizontal_THT +Connector_DIN:DIN41612_R_2x16_Male_Vertical_THT +Connector_DIN:DIN41612_R_2x32_Female_Horizontal_THT +Connector_DIN:DIN41612_R_2x32_Male_Vertical_THT +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_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 +Connector_FFC-FPC:Hirose_FH12-13S-0.5SH_1x13-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-14S-0.5SH_1x14-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-15S-0.5SH_1x15-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-16S-0.5SH_1x16-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-17S-0.5SH_1x17-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-18S-0.5SH_1x18-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-19S-0.5SH_1x19-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-20S-0.5SH_1x20-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-22S-0.5SH_1x22-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-24S-0.5SH_1x24-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-25S-0.5SH_1x25-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-26S-0.5SH_1x26-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-28S-0.5SH_1x28-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-30S-0.5SH_1x30-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-32S-0.5SH_1x32-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-33S-0.5SH_1x33-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-34S-0.5SH_1x34-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-35S-0.5SH_1x35-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-36S-0.5SH_1x36-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-40S-0.5SH_1x40-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH12-45S-0.5SH_1x45-1MP_P0.50mm_Horizontal +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 +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 +Connector_FFC-FPC:Molex_200528-0050_1x05-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0060_1x06-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0070_1x07-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0080_1x08-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0090_1x09-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0100_1x10-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0110_1x11-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0120_1x12-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0130_1x13-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0140_1x14-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0150_1x15-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0160_1x16-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0170_1x17-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0180_1x18-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0190_1x19-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0200_1x20-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0210_1x21-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0220_1x22-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0230_1x23-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0240_1x24-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0250_1x25-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0260_1x26-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0270_1x27-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0280_1x28-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0290_1x29-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_200528-0300_1x30-1MP_P1.00mm_Horizontal +Connector_FFC-FPC:Molex_502231-1500_1x15-1SH_P0.5mm_Vertical +Connector_FFC-FPC:Molex_502231-2400_1x24-1SH_P0.5mm_Vertical +Connector_FFC-FPC:Molex_502231-3300_1x33-1SH_P0.5mm_Vertical +Connector_FFC-FPC:Molex_502244-1530_1x15-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:Molex_502244-2430_1x24-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:Molex_502244-3330_1x33-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:Molex_502250-1791_2Rows-17Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Molex_502250-2191_2Rows-21Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Molex_502250-2391_2Rows-23Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Molex_502250-2791_2Rows-27Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Molex_502250-3391_2Rows-33Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Molex_502250-3591_2Rows-35Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Molex_502250-3991_2Rows-39Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Molex_502250-4191_2Rows-41Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Molex_502250-5191_2Rows-51Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Molex_52559-3652_2x18-1MP_P0.5mm_Vertical +Connector_FFC-FPC:Molex_54132-5033_1x50-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:Molex_54548-1071_1x10-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:Omron_XF2M-4015-1A_1x40-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_0-1734839-5_1x05-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_0-1734839-6_1x06-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_0-1734839-7_1x07-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_0-1734839-8_1x08-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_0-1734839-9_1x09-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_1-1734839-0_1x10-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_1-1734839-1_1x11-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_1-1734839-2_1x12-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_1-1734839-3_1x13-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_1-1734839-4_1x14-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_1-1734839-5_1x15-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_1-1734839-6_1x16-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_1-1734839-7_1x17-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_1-1734839-8_1x18-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_1-1734839-9_1x19-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_1-84952-0_1x10-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84952-1_1x11-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84952-2_1x12-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84952-3_1x13-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84952-4_1x14-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84952-5_1x15-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84952-6_1x16-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84952-7_1x17-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84952-8_1x18-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84952-9_1x19-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84953-0_1x10-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84953-1_1x11-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84953-2_1x12-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84953-3_1x13-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84953-4_1x14-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84953-5_1x15-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84953-6_1x16-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84953-7_1x17-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84953-8_1x18-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_1-84953-9_1x19-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-1734839-0_1x20-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_2-1734839-1_1x21-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_2-1734839-2_1x22-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_2-1734839-3_1x23-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_2-1734839-4_1x24-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_2-1734839-5_1x25-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_2-1734839-6_1x26-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_2-1734839-7_1x27-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_2-1734839-8_1x28-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_2-1734839-9_1x29-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_2-84952-0_1x20-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84952-1_1x21-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84952-2_1x22-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84952-3_1x23-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84952-4_1x24-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84952-5_1x25-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84952-6_1x26-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84952-7_1x27-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84952-8_1x28-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84952-9_1x29-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84953-0_1x20-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84953-1_1x21-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84953-2_1x22-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84953-3_1x23-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84953-4_1x24-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84953-5_1x25-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84953-6_1x26-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84953-7_1x27-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84953-8_1x28-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_2-84953-9_1x29-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_3-1734839-0_1x30-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_3-1734839-1_1x31-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_3-1734839-2_1x32-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_3-1734839-3_1x33-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_3-1734839-4_1x34-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_3-1734839-5_1x35-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_3-1734839-6_1x36-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_3-1734839-7_1x37-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_3-1734839-8_1x38-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_3-1734839-9_1x39-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_3-84952-0_1x30-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_3-84953-0_1x30-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_4-1734839-0_1x40-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_4-1734839-1_1x41-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_4-1734839-2_1x42-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_4-1734839-3_1x43-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_4-1734839-4_1x44-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_4-1734839-5_1x45-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_4-1734839-6_1x46-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_4-1734839-7_1x47-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_4-1734839-8_1x48-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_4-1734839-9_1x49-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_5-1734839-0_1x50-1MP_P0.5mm_Horizontal +Connector_FFC-FPC:TE_84952-4_1x04-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_84952-5_1x05-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_84952-6_1x06-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_84952-7_1x07-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_84952-8_1x08-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_84952-9_1x09-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_84953-4_1x04-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_84953-5_1x05-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_84953-6_1x06-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_84953-7_1x07-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_84953-8_1x08-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:TE_84953-9_1x09-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:Wuerth_68611214422_1x12-1MP_P1.0mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110213001xxx_1x02-MP_P2.54mm_Vertical +Connector_Harting:Harting_har-flexicon_14110213002xxx_1x02-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110213010xxx_1x02-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110313001xxx_1x03-MP_P2.54mm_Vertical +Connector_Harting:Harting_har-flexicon_14110313002xxx_1x03-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110313010xxx_1x03-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110413001xxx_1x04-MP_P2.54mm_Vertical +Connector_Harting:Harting_har-flexicon_14110413002xxx_1x04-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110413010xxx_1x04-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110513001xxx_1x05-MP_P2.54mm_Vertical +Connector_Harting:Harting_har-flexicon_14110513002xxx_1x05-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110513010xxx_1x05-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110613001xxx_1x06-MP_P2.54mm_Vertical +Connector_Harting:Harting_har-flexicon_14110613002xxx_1x06-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110613010xxx_1x06-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110713001xxx_1x07-MP_P2.54mm_Vertical +Connector_Harting:Harting_har-flexicon_14110713002xxx_1x07-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110713010xxx_1x07-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110813001xxx_1x08-MP_P2.54mm_Vertical +Connector_Harting:Harting_har-flexicon_14110813002xxx_1x08-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110813010xxx_1x08-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110913001xxx_1x09-MP_P2.54mm_Vertical +Connector_Harting:Harting_har-flexicon_14110913002xxx_1x09-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14110913010xxx_1x09-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14111013001xxx_1x10-MP_P2.54mm_Vertical +Connector_Harting:Harting_har-flexicon_14111013002xxx_1x10-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14111013010xxx_1x10-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14111113001xxx_1x11-MP_P2.54mm_Vertical +Connector_Harting:Harting_har-flexicon_14111113002xxx_1x11-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14111113010xxx_1x11-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14111213001xxx_1x12-MP_P2.54mm_Vertical +Connector_Harting:Harting_har-flexicon_14111213002xxx_1x12-MP_P2.54mm_Horizontal +Connector_Harting:Harting_har-flexicon_14111213010xxx_1x12-MP_P2.54mm_Horizontal +Connector_Harwin:Harwin_Gecko-G125-FVX0605L0X_2x03_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-FVX1005L0X_2x05_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-FVX1205L0X_2x06_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-FVX1605L0X_2x08_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-FVX2005L0X_2x10_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-FVX2605L0X_2x13_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-FVX3405L0X_2x17_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-FVX5005L0X_2x25_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX0605L0X_2x03_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX0605L1X_2x03_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX1005L0X_2x05_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX1005L1X_2x05_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX1205L0X_2x06_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX1205L1X_2x06_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX1605L0X_2x08_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX1605L1X_2x08_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX2005L0X_2x10_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX2005L1X_2x10_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX2605L0X_2x13_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX2605L1X_2x13_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX3405L0X_2x17_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX3405L1X_2x17_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX5005L0X_2x25_P1.25mm_Vertical +Connector_Harwin:Harwin_Gecko-G125-MVX5005L1X_2x25_P1.25mm_Vertical +Connector_Harwin:Harwin_LTek-Male_02_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_02_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_03_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_03_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_04_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_04_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_05_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_05_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_06_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_06_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_07_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_07_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_17_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_17_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_22_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_22_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_2x02_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_2x02_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_2x03_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_2x03_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_2x04_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_2x04_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_2x05_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_2x05_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_2x06_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_2x06_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_2x07_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_2x07_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_2x08_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_2x08_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_2x09_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_2x09_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_2x10_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_2x10_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_2x13_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_2x13_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_2x17_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_2x17_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_LTek-Male_2x22_P2.00mm_Vertical +Connector_Harwin:Harwin_LTek-Male_2x22_P2.00mm_Vertical_StrainRelief +Connector_Harwin:Harwin_M20-7810245_2x02_P2.54mm_Vertical +Connector_Harwin:Harwin_M20-7810345_2x03_P2.54mm_Vertical +Connector_Harwin:Harwin_M20-7810445_2x04_P2.54mm_Vertical +Connector_Harwin:Harwin_M20-7810545_2x05_P2.54mm_Vertical +Connector_Harwin:Harwin_M20-7810645_2x06_P2.54mm_Vertical +Connector_Harwin:Harwin_M20-7810745_2x07_P2.54mm_Vertical +Connector_Harwin:Harwin_M20-7810845_2x08_P2.54mm_Vertical +Connector_Harwin:Harwin_M20-7810945_2x09_P2.54mm_Vertical +Connector_Harwin:Harwin_M20-7811045_2x10_P2.54mm_Vertical +Connector_Harwin:Harwin_M20-7811245_2x12_P2.54mm_Vertical +Connector_Harwin:Harwin_M20-7811545_2x15_P2.54mm_Vertical +Connector_Harwin:Harwin_M20-7812045_2x20_P2.54mm_Vertical +Connector_Harwin:Harwin_M20-89003xx_1x03_P2.54mm_Horizontal +Connector_Harwin:Harwin_M20-89004xx_1x04_P2.54mm_Horizontal +Connector_Harwin:Harwin_M20-89005xx_1x05_P2.54mm_Horizontal +Connector_Harwin:Harwin_M20-89006xx_1x06_P2.54mm_Horizontal +Connector_Harwin:Harwin_M20-89007xx_1x07_P2.54mm_Horizontal +Connector_Harwin:Harwin_M20-89008xx_1x08_P2.54mm_Horizontal +Connector_Harwin:Harwin_M20-89009xx_1x09_P2.54mm_Horizontal +Connector_Harwin:Harwin_M20-89010xx_1x10_P2.54mm_Horizontal +Connector_Harwin:Harwin_M20-89011xx_1x11_P2.54mm_Horizontal +Connector_Harwin:Harwin_M20-89012xx_1x12_P2.54mm_Horizontal +Connector_Harwin:Harwin_M20-89013xx_1x13_P2.54mm_Horizontal +Connector_Harwin:Harwin_M20-89014xx_1x14_P2.54mm_Horizontal +Connector_Harwin:Harwin_M20-89015xx_1x15_P2.54mm_Horizontal +Connector_Harwin:Harwin_M20-89016xx_1x16_P2.54mm_Horizontal +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_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 +Connector_Hirose:Hirose_DF11-12DP-2DSA_2x06_P2.00mm_Vertical +Connector_Hirose:Hirose_DF11-14DP-2DSA_2x07_P2.00mm_Vertical +Connector_Hirose:Hirose_DF11-16DP-2DSA_2x08_P2.00mm_Vertical +Connector_Hirose:Hirose_DF11-18DP-2DSA_2x09_P2.00mm_Vertical +Connector_Hirose:Hirose_DF11-20DP-2DSA_2x10_P2.00mm_Vertical +Connector_Hirose:Hirose_DF11-22DP-2DSA_2x11_P2.00mm_Vertical +Connector_Hirose:Hirose_DF11-24DP-2DSA_2x12_P2.00mm_Vertical +Connector_Hirose:Hirose_DF11-26DP-2DSA_2x13_P2.00mm_Vertical +Connector_Hirose:Hirose_DF11-28DP-2DSA_2x14_P2.00mm_Vertical +Connector_Hirose:Hirose_DF11-30DP-2DSA_2x15_P2.00mm_Vertical +Connector_Hirose:Hirose_DF11-32DP-2DSA_2x16_P2.00mm_Vertical +Connector_Hirose:Hirose_DF11-4DP-2DSA_2x02_P2.00mm_Vertical +Connector_Hirose:Hirose_DF11-6DP-2DSA_2x03_P2.00mm_Vertical +Connector_Hirose:Hirose_DF11-8DP-2DSA_2x04_P2.00mm_Vertical +Connector_Hirose:Hirose_DF12_DF12C3.0-10DS-0.5V_2x05_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12C3.0-14DS-0.5V_2x07_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12C3.0-20DS-0.5V_2x10_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12C3.0-30DS-0.5V_2x15_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12C3.0-32DS-0.5V_2x16_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12C3.0-36DS-0.5V_2x18_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12C3.0-40DS-0.5V_2x20_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12C3.0-50DS-0.5V_2x25_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12C3.0-60DS-0.5V_2x30_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12E3.0-10DP-0.5V_2x05_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12E3.0-14DP-0.5V_2x07_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12E3.0-20DP-0.5V_2x10_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12E3.0-30DP-0.5V_2x15_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12E3.0-32DP-0.5V_2x16_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12E3.0-36DP-0.5V_2x18_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12E3.0-40DP-0.5V_2x20_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12E3.0-50DP-0.5V_2x25_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12E3.0-60DP-0.5V_2x30_P0.50mm_Vertical +Connector_Hirose:Hirose_DF12_DF12E3.0-80DP-0.5V_2x40_P0.50mm_Vertical +Connector_Hirose:Hirose_DF13-02P-1.25DSA_1x02_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13-02P-1.25DS_1x02_P1.25mm_Horizontal +Connector_Hirose:Hirose_DF13-03P-1.25DSA_1x03_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13-03P-1.25DS_1x03_P1.25mm_Horizontal +Connector_Hirose:Hirose_DF13-04P-1.25DSA_1x04_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13-04P-1.25DS_1x04_P1.25mm_Horizontal +Connector_Hirose:Hirose_DF13-05P-1.25DSA_1x05_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13-05P-1.25DS_1x05_P1.25mm_Horizontal +Connector_Hirose:Hirose_DF13-06P-1.25DSA_1x06_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13-06P-1.25DS_1x06_P1.25mm_Horizontal +Connector_Hirose:Hirose_DF13-07P-1.25DSA_1x07_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13-07P-1.25DS_1x07_P1.25mm_Horizontal +Connector_Hirose:Hirose_DF13-08P-1.25DSA_1x08_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13-08P-1.25DS_1x08_P1.25mm_Horizontal +Connector_Hirose:Hirose_DF13-09P-1.25DSA_1x09_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13-09P-1.25DS_1x09_P1.25mm_Horizontal +Connector_Hirose:Hirose_DF13-10P-1.25DSA_1x10_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13-10P-1.25DS_1x10_P1.25mm_Horizontal +Connector_Hirose:Hirose_DF13-11P-1.25DSA_1x11_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13-11P-1.25DS_1x11_P1.25mm_Horizontal +Connector_Hirose:Hirose_DF13-12P-1.25DSA_1x12_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13-12P-1.25DS_1x12_P1.25mm_Horizontal +Connector_Hirose:Hirose_DF13-13P-1.25DSA_1x13_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13-14P-1.25DSA_1x14_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13-14P-1.25DS_1x14_P1.25mm_Horizontal +Connector_Hirose:Hirose_DF13-15P-1.25DSA_1x15_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13-15P-1.25DS_1x15_P1.25mm_Horizontal +Connector_Hirose:Hirose_DF13C_CL535-0402-2-51_1x02-1MP_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13C_CL535-0403-5-51_1x03-1MP_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13C_CL535-0404-8-51_1x04-1MP_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13C_CL535-0405-0-51_1x05-1MP_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13C_CL535-0406-3-51_1x06-1MP_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13C_CL535-0407-6-51_1x07-1MP_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13C_CL535-0408-9-51_1x08-1MP_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13C_CL535-0409-1-51_1x09-1MP_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13C_CL535-0410-4-51_1x10-1MP_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13C_CL535-0411-3-51_1x11-1MP_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13C_CL535-0412-6-51_1x12-1MP_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13C_CL535-0414-1-51_1x14-1MP_P1.25mm_Vertical +Connector_Hirose:Hirose_DF13C_CL535-0415-4-51_1x15-1MP_P1.25mm_Vertical +Connector_Hirose:Hirose_DF3EA-02P-2H_1x02-1MP_P2.00mm_Horizontal +Connector_Hirose:Hirose_DF3EA-03P-2H_1x03-1MP_P2.00mm_Horizontal +Connector_Hirose:Hirose_DF3EA-04P-2H_1x04-1MP_P2.00mm_Horizontal +Connector_Hirose:Hirose_DF3EA-05P-2H_1x05-1MP_P2.00mm_Horizontal +Connector_Hirose:Hirose_DF3EA-06P-2H_1x06-1MP_P2.00mm_Horizontal +Connector_Hirose:Hirose_DF3EA-07P-2H_1x07-1MP_P2.00mm_Horizontal +Connector_Hirose:Hirose_DF3EA-08P-2H_1x08-1MP_P2.00mm_Horizontal +Connector_Hirose:Hirose_DF3EA-09P-2H_1x09-1MP_P2.00mm_Horizontal +Connector_Hirose:Hirose_DF3EA-10P-2H_1x10-1MP_P2.00mm_Horizontal +Connector_Hirose:Hirose_DF3EA-11P-2H_1x11-1MP_P2.00mm_Horizontal +Connector_Hirose:Hirose_DF3EA-12P-2H_1x12-1MP_P2.00mm_Horizontal +Connector_Hirose:Hirose_DF3EA-13P-2H_1x13-1MP_P2.00mm_Horizontal +Connector_Hirose:Hirose_DF3EA-14P-2H_1x14-1MP_P2.00mm_Horizontal +Connector_Hirose:Hirose_DF3EA-15P-2H_1x15-1MP_P2.00mm_Horizontal +Connector_Hirose:Hirose_DF52-10S-0.8H_1x10-1MP_P0.80mm_Horizontal +Connector_Hirose:Hirose_DF52-11S-0.8H_1x11-1MP_P0.80mm_Horizontal +Connector_Hirose:Hirose_DF52-12S-0.8H_1x12-1MP_P0.80mm_Horizontal +Connector_Hirose:Hirose_DF52-14S-0.8H_1x14-1MP_P0.80mm_Horizontal +Connector_Hirose:Hirose_DF52-15S-0.8H_1x15-1MP_P0.80mm_Horizontal +Connector_Hirose:Hirose_DF52-2S-0.8H_1x02-1MP_P0.80mm_Horizontal +Connector_Hirose:Hirose_DF52-3S-0.8H_1x03-1MP_P0.80mm_Horizontal +Connector_Hirose:Hirose_DF52-4S-0.8H_1x04-1MP_P0.80mm_Horizontal +Connector_Hirose:Hirose_DF52-5S-0.8H_1x05-1MP_P0.80mm_Horizontal +Connector_Hirose:Hirose_DF52-6S-0.8H_1x06-1MP_P0.80mm_Horizontal +Connector_Hirose:Hirose_DF52-7S-0.8H_1x07-1MP_P0.80mm_Horizontal +Connector_Hirose:Hirose_DF52-8S-0.8H_1x08-1MP_P0.80mm_Horizontal +Connector_Hirose:Hirose_DF52-9S-0.8H_1x09-1MP_P0.80mm_Horizontal +Connector_Hirose:Hirose_DF63-5P-3.96DSA_1x05_P3.96mm_Vertical +Connector_Hirose:Hirose_DF63-6P-3.96DSA_1x06_P3.96mm_Vertical +Connector_Hirose:Hirose_DF63M-1P-3.96DSA_1x01_P3.96mm_Vertical +Connector_Hirose:Hirose_DF63M-2P-3.96DSA_1x02_P3.96mm_Vertical +Connector_Hirose:Hirose_DF63M-3P-3.96DSA_1x03_P3.96mm_Vertical +Connector_Hirose:Hirose_DF63M-4P-3.96DSA_1x04_P3.96mm_Vertical +Connector_Hirose:Hirose_DF63R-1P-3.96DSA_1x01_P3.96mm_Vertical +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 +Connector_IDC:IDC-Header_2x04_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x04_P2.54mm_Vertical +Connector_IDC:IDC-Header_2x04_P2.54mm_Vertical_SMD +Connector_IDC:IDC-Header_2x05-1MP_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x05-1MP_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x05-1MP_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x05-1MP_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x05-1MP_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x05_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x05_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x05_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x05_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x05_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x05_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x05_P2.54mm_Vertical +Connector_IDC:IDC-Header_2x05_P2.54mm_Vertical_SMD +Connector_IDC:IDC-Header_2x06-1MP_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x06-1MP_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x06-1MP_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x06-1MP_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x06-1MP_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x06_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x06_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x06_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x06_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x06_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x06_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x06_P2.54mm_Vertical +Connector_IDC:IDC-Header_2x06_P2.54mm_Vertical_SMD +Connector_IDC:IDC-Header_2x07-1MP_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x07-1MP_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x07-1MP_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x07-1MP_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x07-1MP_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x07_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x07_P2.54mm_Horizontal_Lock +Connector_IDC:IDC-Header_2x07_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x07_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x07_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x07_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x07_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x07_P2.54mm_Vertical +Connector_IDC:IDC-Header_2x07_P2.54mm_Vertical_SMD +Connector_IDC:IDC-Header_2x08-1MP_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x08-1MP_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x08-1MP_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x08-1MP_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x08-1MP_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x08_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x08_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x08_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x08_P2.54mm_Latch9.5mm_Vertical +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 +Connector_IDC:IDC-Header_2x10-1MP_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x10-1MP_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x10_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x10_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x10_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x10_P2.54mm_Latch9.5mm_Vertical +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 +Connector_IDC:IDC-Header_2x12-1MP_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x12-1MP_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x12_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x12_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x12_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x12_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x12_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x12_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x12_P2.54mm_Vertical +Connector_IDC:IDC-Header_2x12_P2.54mm_Vertical_SMD +Connector_IDC:IDC-Header_2x13-1MP_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x13-1MP_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x13-1MP_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x13-1MP_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x13-1MP_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x13_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x13_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x13_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x13_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x13_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x13_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x13_P2.54mm_Vertical +Connector_IDC:IDC-Header_2x13_P2.54mm_Vertical_SMD +Connector_IDC:IDC-Header_2x15-1MP_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x15-1MP_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x15-1MP_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x15-1MP_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x15-1MP_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x15_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x15_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x15_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x15_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x15_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x15_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x15_P2.54mm_Vertical +Connector_IDC:IDC-Header_2x17-1MP_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x17-1MP_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x17-1MP_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x17-1MP_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x17-1MP_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x17_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x17_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x17_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x17_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x17_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x17_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x17_P2.54mm_Vertical +Connector_IDC:IDC-Header_2x20-1MP_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x20-1MP_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x20-1MP_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x20-1MP_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x20-1MP_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x20_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x20_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x20_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x20_P2.54mm_Latch9.5mm_Vertical +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 +Connector_IDC:IDC-Header_2x25-1MP_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x25-1MP_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x25_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x25_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x25_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x25_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x25_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x25_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x25_P2.54mm_Vertical +Connector_IDC:IDC-Header_2x25_P2.54mm_Vertical_SMD +Connector_IDC:IDC-Header_2x30-1MP_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x30-1MP_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x30-1MP_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x30-1MP_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x30-1MP_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x30_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x30_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x30_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x30_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x30_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x30_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x30_P2.54mm_Vertical +Connector_IDC:IDC-Header_2x30_P2.54mm_Vertical_SMD +Connector_IDC:IDC-Header_2x32-1MP_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x32-1MP_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x32-1MP_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x32-1MP_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x32-1MP_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x32_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x32_P2.54mm_Latch12.0mm_Vertical +Connector_IDC:IDC-Header_2x32_P2.54mm_Latch6.5mm_Vertical +Connector_IDC:IDC-Header_2x32_P2.54mm_Latch9.5mm_Vertical +Connector_IDC:IDC-Header_2x32_P2.54mm_Latch_Horizontal +Connector_IDC:IDC-Header_2x32_P2.54mm_Latch_Vertical +Connector_IDC:IDC-Header_2x32_P2.54mm_Vertical +Connector_JAE:JAE_LY20-10P-DLT1_2x05_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-10P-DT1_2x05_P2.00mm_Vertical +Connector_JAE:JAE_LY20-12P-DLT1_2x06_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-12P-DT1_2x06_P2.00mm_Vertical +Connector_JAE:JAE_LY20-14P-DLT1_2x07_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-14P-DT1_2x07_P2.00mm_Vertical +Connector_JAE:JAE_LY20-16P-DLT1_2x08_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-16P-DT1_2x08_P2.00mm_Vertical +Connector_JAE:JAE_LY20-18P-DLT1_2x09_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-18P-DT1_2x09_P2.00mm_Vertical +Connector_JAE:JAE_LY20-20P-DLT1_2x10_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-20P-DT1_2x10_P2.00mm_Vertical +Connector_JAE:JAE_LY20-22P-DLT1_2x11_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-22P-DT1_2x11_P2.00mm_Vertical +Connector_JAE:JAE_LY20-24P-DLT1_2x12_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-24P-DT1_2x12_P2.00mm_Vertical +Connector_JAE:JAE_LY20-26P-DLT1_2x13_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-26P-DT1_2x13_P2.00mm_Vertical +Connector_JAE:JAE_LY20-28P-DLT1_2x14_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-28P-DT1_2x14_P2.00mm_Vertical +Connector_JAE:JAE_LY20-30P-DLT1_2x15_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-30P-DT1_2x15_P2.00mm_Vertical +Connector_JAE:JAE_LY20-32P-DLT1_2x16_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-32P-DT1_2x16_P2.00mm_Vertical +Connector_JAE:JAE_LY20-34P-DLT1_2x17_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-34P-DT1_2x17_P2.00mm_Vertical +Connector_JAE:JAE_LY20-36P-DLT1_2x18_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-36P-DT1_2x18_P2.00mm_Vertical +Connector_JAE:JAE_LY20-38P-DLT1_2x19_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-38P-DT1_2x19_P2.00mm_Vertical +Connector_JAE:JAE_LY20-40P-DLT1_2x20_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-40P-DT1_2x20_P2.00mm_Vertical +Connector_JAE:JAE_LY20-42P-DLT1_2x21_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-42P-DT1_2x21_P2.00mm_Vertical +Connector_JAE:JAE_LY20-44P-DLT1_2x22_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-44P-DT1_2x22_P2.00mm_Vertical +Connector_JAE:JAE_LY20-4P-DLT1_2x02_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-4P-DT1_2x02_P2.00mm_Vertical +Connector_JAE:JAE_LY20-6P-DLT1_2x03_P2.00mm_Horizontal +Connector_JAE:JAE_LY20-6P-DT1_2x03_P2.00mm_Vertical +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 +Connector_JST:JST_ACH_BM04B-ACHSS-A-GAN-ETF_1x04-1MP_P1.20mm_Vertical +Connector_JST:JST_ACH_BM05B-ACHSS-A-GAN-ETF_1x05-1MP_P1.20mm_Vertical +Connector_JST:JST_AUH_BM03B-AUHKS-GA-TB_1x03-1MP_P1.50mm_Vertical +Connector_JST:JST_AUH_BM05B-AUHKS-GA-TB_1x05-1MP_P1.50mm_Vertical +Connector_JST:JST_EH_B10B-EH-A_1x10_P2.50mm_Vertical +Connector_JST:JST_EH_B11B-EH-A_1x11_P2.50mm_Vertical +Connector_JST:JST_EH_B12B-EH-A_1x12_P2.50mm_Vertical +Connector_JST:JST_EH_B13B-EH-A_1x13_P2.50mm_Vertical +Connector_JST:JST_EH_B14B-EH-A_1x14_P2.50mm_Vertical +Connector_JST:JST_EH_B15B-EH-A_1x15_P2.50mm_Vertical +Connector_JST:JST_EH_B2B-EH-A_1x02_P2.50mm_Vertical +Connector_JST:JST_EH_B3B-EH-A_1x03_P2.50mm_Vertical +Connector_JST:JST_EH_B4B-EH-A_1x04_P2.50mm_Vertical +Connector_JST:JST_EH_B5B-EH-A_1x05_P2.50mm_Vertical +Connector_JST:JST_EH_B6B-EH-A_1x06_P2.50mm_Vertical +Connector_JST:JST_EH_B7B-EH-A_1x07_P2.50mm_Vertical +Connector_JST:JST_EH_B8B-EH-A_1x08_P2.50mm_Vertical +Connector_JST:JST_EH_B9B-EH-A_1x09_P2.50mm_Vertical +Connector_JST:JST_EH_S10B-EH_1x10_P2.50mm_Horizontal +Connector_JST:JST_EH_S11B-EH_1x11_P2.50mm_Horizontal +Connector_JST:JST_EH_S12B-EH_1x12_P2.50mm_Horizontal +Connector_JST:JST_EH_S13B-EH_1x13_P2.50mm_Horizontal +Connector_JST:JST_EH_S14B-EH_1x14_P2.50mm_Horizontal +Connector_JST:JST_EH_S15B-EH_1x15_P2.50mm_Horizontal +Connector_JST:JST_EH_S2B-EH_1x02_P2.50mm_Horizontal +Connector_JST:JST_EH_S3B-EH_1x03_P2.50mm_Horizontal +Connector_JST:JST_EH_S4B-EH_1x04_P2.50mm_Horizontal +Connector_JST:JST_EH_S5B-EH_1x05_P2.50mm_Horizontal +Connector_JST:JST_EH_S6B-EH_1x06_P2.50mm_Horizontal +Connector_JST:JST_EH_S7B-EH_1x07_P2.50mm_Horizontal +Connector_JST:JST_EH_S8B-EH_1x08_P2.50mm_Horizontal +Connector_JST:JST_EH_S9B-EH_1x09_P2.50mm_Horizontal +Connector_JST:JST_GH_BM02B-GHS-TBT_1x02-1MP_P1.25mm_Vertical +Connector_JST:JST_GH_BM03B-GHS-TBT_1x03-1MP_P1.25mm_Vertical +Connector_JST:JST_GH_BM04B-GHS-TBT_1x04-1MP_P1.25mm_Vertical +Connector_JST:JST_GH_BM05B-GHS-TBT_1x05-1MP_P1.25mm_Vertical +Connector_JST:JST_GH_BM06B-GHS-TBT_1x06-1MP_P1.25mm_Vertical +Connector_JST:JST_GH_BM07B-GHS-TBT_1x07-1MP_P1.25mm_Vertical +Connector_JST:JST_GH_BM08B-GHS-TBT_1x08-1MP_P1.25mm_Vertical +Connector_JST:JST_GH_BM09B-GHS-TBT_1x09-1MP_P1.25mm_Vertical +Connector_JST:JST_GH_BM10B-GHS-TBT_1x10-1MP_P1.25mm_Vertical +Connector_JST:JST_GH_BM11B-GHS-TBT_1x11-1MP_P1.25mm_Vertical +Connector_JST:JST_GH_BM12B-GHS-TBT_1x12-1MP_P1.25mm_Vertical +Connector_JST:JST_GH_BM13B-GHS-TBT_1x13-1MP_P1.25mm_Vertical +Connector_JST:JST_GH_BM14B-GHS-TBT_1x14-1MP_P1.25mm_Vertical +Connector_JST:JST_GH_BM15B-GHS-TBT_1x15-1MP_P1.25mm_Vertical +Connector_JST:JST_GH_SM02B-GHS-TB_1x02-1MP_P1.25mm_Horizontal +Connector_JST:JST_GH_SM03B-GHS-TB_1x03-1MP_P1.25mm_Horizontal +Connector_JST:JST_GH_SM04B-GHS-TB_1x04-1MP_P1.25mm_Horizontal +Connector_JST:JST_GH_SM05B-GHS-TB_1x05-1MP_P1.25mm_Horizontal +Connector_JST:JST_GH_SM06B-GHS-TB_1x06-1MP_P1.25mm_Horizontal +Connector_JST:JST_GH_SM07B-GHS-TB_1x07-1MP_P1.25mm_Horizontal +Connector_JST:JST_GH_SM08B-GHS-TB_1x08-1MP_P1.25mm_Horizontal +Connector_JST:JST_GH_SM09B-GHS-TB_1x09-1MP_P1.25mm_Horizontal +Connector_JST:JST_GH_SM10B-GHS-TB_1x10-1MP_P1.25mm_Horizontal +Connector_JST:JST_GH_SM11B-GHS-TB_1x11-1MP_P1.25mm_Horizontal +Connector_JST:JST_GH_SM12B-GHS-TB_1x12-1MP_P1.25mm_Horizontal +Connector_JST:JST_GH_SM13B-GHS-TB_1x13-1MP_P1.25mm_Horizontal +Connector_JST:JST_GH_SM14B-GHS-TB_1x14-1MP_P1.25mm_Horizontal +Connector_JST:JST_GH_SM15B-GHS-TB_1x15-1MP_P1.25mm_Horizontal +Connector_JST:JST_J2100_B06B-J21DK-GGXR_2x03_P2.50x4.00mm_Vertical +Connector_JST:JST_J2100_B08B-J21DK-GGXR_2x04_P2.50x4.00mm_Vertical +Connector_JST:JST_J2100_B10B-J21DK-GGXR_2x05_P2.50x4.00mm_Vertical +Connector_JST:JST_J2100_B12B-J21DK-GGXR_2x06_P2.50x4.00mm_Vertical +Connector_JST:JST_J2100_B16B-J21DK-GGXR_2x08_P2.50x4.00mm_Vertical +Connector_JST:JST_J2100_B20B-J21DK-GGXR_2x10_P2.50x4.00mm_Vertical +Connector_JST:JST_J2100_S06B-J21DK-GGXR_2x03_P2.50mm_Horizontal +Connector_JST:JST_J2100_S08B-J21DK-GGXR_2x04_P2.50mm_Horizontal +Connector_JST:JST_J2100_S10B-J21DK-GGXR_2x05_P2.50mm_Horizontal +Connector_JST:JST_J2100_S12B-J21DK-GGXR_2x06_P2.50mm_Horizontal +Connector_JST:JST_J2100_S16B-J21DK-GGXR_2x08_P2.50mm_Horizontal +Connector_JST:JST_J2100_S20B-J21DK-GGXR_2x10_P2.50mm_Horizontal +Connector_JST:JST_JWPF_B02B-JWPF-SK-R_1x02_P2.00mm_Vertical +Connector_JST:JST_JWPF_B03B-JWPF-SK-R_1x03_P2.00mm_Vertical +Connector_JST:JST_JWPF_B04B-JWPF-SK-R_1x04_P2.00mm_Vertical +Connector_JST:JST_JWPF_B06B-JWPF-SK-R_2x03_P2.00mm_Vertical +Connector_JST:JST_JWPF_B08B-JWPF-SK-R_2x04_P2.00mm_Vertical +Connector_JST:JST_LEA_SM02B-LEASS-TF_1x02-1MP_P4.20mm_Horizontal +Connector_JST:JST_NV_B02P-NV_1x02_P5.00mm_Vertical +Connector_JST:JST_NV_B03P-NV_1x03_P5.00mm_Vertical +Connector_JST:JST_NV_B04P-NV_1x04_P5.00mm_Vertical +Connector_JST:JST_PHD_B10B-PHDSS_2x05_P2.00mm_Vertical +Connector_JST:JST_PHD_B12B-PHDSS_2x06_P2.00mm_Vertical +Connector_JST:JST_PHD_B14B-PHDSS_2x07_P2.00mm_Vertical +Connector_JST:JST_PHD_B16B-PHDSS_2x08_P2.00mm_Vertical +Connector_JST:JST_PHD_B18B-PHDSS_2x09_P2.00mm_Vertical +Connector_JST:JST_PHD_B20B-PHDSS_2x10_P2.00mm_Vertical +Connector_JST:JST_PHD_B22B-PHDSS_2x11_P2.00mm_Vertical +Connector_JST:JST_PHD_B24B-PHDSS_2x12_P2.00mm_Vertical +Connector_JST:JST_PHD_B26B-PHDSS_2x13_P2.00mm_Vertical +Connector_JST:JST_PHD_B28B-PHDSS_2x14_P2.00mm_Vertical +Connector_JST:JST_PHD_B30B-PHDSS_2x15_P2.00mm_Vertical +Connector_JST:JST_PHD_B32B-PHDSS_2x16_P2.00mm_Vertical +Connector_JST:JST_PHD_B34B-PHDSS_2x17_P2.00mm_Vertical +Connector_JST:JST_PHD_B8B-PHDSS_2x04_P2.00mm_Vertical +Connector_JST:JST_PHD_S10B-PHDSS_2x05_P2.00mm_Horizontal +Connector_JST:JST_PHD_S12B-PHDSS_2x06_P2.00mm_Horizontal +Connector_JST:JST_PHD_S14B-PHDSS_2x07_P2.00mm_Horizontal +Connector_JST:JST_PHD_S16B-PHDSS_2x08_P2.00mm_Horizontal +Connector_JST:JST_PHD_S18B-PHDSS_2x09_P2.00mm_Horizontal +Connector_JST:JST_PHD_S20B-PHDSS_2x10_P2.00mm_Horizontal +Connector_JST:JST_PHD_S22B-PHDSS_2x11_P2.00mm_Horizontal +Connector_JST:JST_PHD_S24B-PHDSS_2x12_P2.00mm_Horizontal +Connector_JST:JST_PHD_S26B-PHDSS_2x13_P2.00mm_Horizontal +Connector_JST:JST_PHD_S28B-PHDSS_2x14_P2.00mm_Horizontal +Connector_JST:JST_PHD_S30B-PHDSS_2x15_P2.00mm_Horizontal +Connector_JST:JST_PHD_S32B-PHDSS_2x16_P2.00mm_Horizontal +Connector_JST:JST_PHD_S34B-PHDSS_2x17_P2.00mm_Horizontal +Connector_JST:JST_PHD_S8B-PHDSS_2x04_P2.00mm_Horizontal +Connector_JST:JST_PH_B10B-PH-K_1x10_P2.00mm_Vertical +Connector_JST:JST_PH_B10B-PH-SM4-TB_1x10-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_B11B-PH-K_1x11_P2.00mm_Vertical +Connector_JST:JST_PH_B11B-PH-SM4-TB_1x11-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_B12B-PH-K_1x12_P2.00mm_Vertical +Connector_JST:JST_PH_B12B-PH-SM4-TB_1x12-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_B13B-PH-K_1x13_P2.00mm_Vertical +Connector_JST:JST_PH_B13B-PH-SM4-TB_1x13-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_B14B-PH-K_1x14_P2.00mm_Vertical +Connector_JST:JST_PH_B14B-PH-SM4-TB_1x14-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_B15B-PH-K_1x15_P2.00mm_Vertical +Connector_JST:JST_PH_B15B-PH-SM4-TB_1x15-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_B16B-PH-K_1x16_P2.00mm_Vertical +Connector_JST:JST_PH_B16B-PH-SM4-TB_1x16-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_B2B-PH-K_1x02_P2.00mm_Vertical +Connector_JST:JST_PH_B2B-PH-SM4-TB_1x02-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_B3B-PH-K_1x03_P2.00mm_Vertical +Connector_JST:JST_PH_B3B-PH-SM4-TB_1x03-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_B4B-PH-K_1x04_P2.00mm_Vertical +Connector_JST:JST_PH_B4B-PH-SM4-TB_1x04-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_B5B-PH-K_1x05_P2.00mm_Vertical +Connector_JST:JST_PH_B5B-PH-SM4-TB_1x05-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_B6B-PH-K_1x06_P2.00mm_Vertical +Connector_JST:JST_PH_B6B-PH-SM4-TB_1x06-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_B7B-PH-K_1x07_P2.00mm_Vertical +Connector_JST:JST_PH_B7B-PH-SM4-TB_1x07-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_B8B-PH-K_1x08_P2.00mm_Vertical +Connector_JST:JST_PH_B8B-PH-SM4-TB_1x08-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_B9B-PH-K_1x09_P2.00mm_Vertical +Connector_JST:JST_PH_B9B-PH-SM4-TB_1x09-1MP_P2.00mm_Vertical +Connector_JST:JST_PH_S10B-PH-K_1x10_P2.00mm_Horizontal +Connector_JST:JST_PH_S10B-PH-SM4-TB_1x10-1MP_P2.00mm_Horizontal +Connector_JST:JST_PH_S11B-PH-K_1x11_P2.00mm_Horizontal +Connector_JST:JST_PH_S11B-PH-SM4-TB_1x11-1MP_P2.00mm_Horizontal +Connector_JST:JST_PH_S12B-PH-K_1x12_P2.00mm_Horizontal +Connector_JST:JST_PH_S12B-PH-SM4-TB_1x12-1MP_P2.00mm_Horizontal +Connector_JST:JST_PH_S13B-PH-K_1x13_P2.00mm_Horizontal +Connector_JST:JST_PH_S13B-PH-SM4-TB_1x13-1MP_P2.00mm_Horizontal +Connector_JST:JST_PH_S14B-PH-K_1x14_P2.00mm_Horizontal +Connector_JST:JST_PH_S14B-PH-SM4-TB_1x14-1MP_P2.00mm_Horizontal +Connector_JST:JST_PH_S15B-PH-K_1x15_P2.00mm_Horizontal +Connector_JST:JST_PH_S15B-PH-SM4-TB_1x15-1MP_P2.00mm_Horizontal +Connector_JST:JST_PH_S16B-PH-K_1x16_P2.00mm_Horizontal +Connector_JST:JST_PH_S2B-PH-K_1x02_P2.00mm_Horizontal +Connector_JST:JST_PH_S2B-PH-SM4-TB_1x02-1MP_P2.00mm_Horizontal +Connector_JST:JST_PH_S3B-PH-K_1x03_P2.00mm_Horizontal +Connector_JST:JST_PH_S3B-PH-SM4-TB_1x03-1MP_P2.00mm_Horizontal +Connector_JST:JST_PH_S4B-PH-K_1x04_P2.00mm_Horizontal +Connector_JST:JST_PH_S4B-PH-SM4-TB_1x04-1MP_P2.00mm_Horizontal +Connector_JST:JST_PH_S5B-PH-K_1x05_P2.00mm_Horizontal +Connector_JST:JST_PH_S5B-PH-SM4-TB_1x05-1MP_P2.00mm_Horizontal +Connector_JST:JST_PH_S6B-PH-K_1x06_P2.00mm_Horizontal +Connector_JST:JST_PH_S6B-PH-SM4-TB_1x06-1MP_P2.00mm_Horizontal +Connector_JST:JST_PH_S7B-PH-K_1x07_P2.00mm_Horizontal +Connector_JST:JST_PH_S7B-PH-SM4-TB_1x07-1MP_P2.00mm_Horizontal +Connector_JST:JST_PH_S8B-PH-K_1x08_P2.00mm_Horizontal +Connector_JST:JST_PH_S8B-PH-SM4-TB_1x08-1MP_P2.00mm_Horizontal +Connector_JST:JST_PH_S9B-PH-K_1x09_P2.00mm_Horizontal +Connector_JST:JST_PH_S9B-PH-SM4-TB_1x09-1MP_P2.00mm_Horizontal +Connector_JST:JST_PUD_B08B-PUDSS_2x04_P2.00mm_Vertical +Connector_JST:JST_PUD_B10B-PUDSS_2x05_P2.00mm_Vertical +Connector_JST:JST_PUD_B12B-PUDSS_2x06_P2.00mm_Vertical +Connector_JST:JST_PUD_B14B-PUDSS_2x07_P2.00mm_Vertical +Connector_JST:JST_PUD_B16B-PUDSS_2x08_P2.00mm_Vertical +Connector_JST:JST_PUD_B18B-PUDSS_2x09_P2.00mm_Vertical +Connector_JST:JST_PUD_B20B-PUDSS_2x10_P2.00mm_Vertical +Connector_JST:JST_PUD_B22B-PUDSS_2x11_P2.00mm_Vertical +Connector_JST:JST_PUD_B24B-PUDSS_2x12_P2.00mm_Vertical +Connector_JST:JST_PUD_B26B-PUDSS_2x13_P2.00mm_Vertical +Connector_JST:JST_PUD_B28B-PUDSS_2x14_P2.00mm_Vertical +Connector_JST:JST_PUD_B30B-PUDSS_2x15_P2.00mm_Vertical +Connector_JST:JST_PUD_B32B-PUDSS_2x16_P2.00mm_Vertical +Connector_JST:JST_PUD_B34B-PUDSS_2x17_P2.00mm_Vertical +Connector_JST:JST_PUD_B36B-PUDSS_2x18_P2.00mm_Vertical +Connector_JST:JST_PUD_B38B-PUDSS_2x19_P2.00mm_Vertical +Connector_JST:JST_PUD_B40B-PUDSS_2x20_P2.00mm_Vertical +Connector_JST:JST_PUD_S08B-PUDSS-1_2x04_P2.00mm_Horizontal +Connector_JST:JST_PUD_S10B-PUDSS-1_2x05_P2.00mm_Horizontal +Connector_JST:JST_PUD_S12B-PUDSS-1_2x06_P2.00mm_Horizontal +Connector_JST:JST_PUD_S14B-PUDSS-1_2x07_P2.00mm_Horizontal +Connector_JST:JST_PUD_S16B-PUDSS-1_2x08_P2.00mm_Horizontal +Connector_JST:JST_PUD_S18B-PUDSS-1_2x09_P2.00mm_Horizontal +Connector_JST:JST_PUD_S20B-PUDSS-1_2x10_P2.00mm_Horizontal +Connector_JST:JST_PUD_S22B-PUDSS-1_2x11_P2.00mm_Horizontal +Connector_JST:JST_PUD_S24B-PUDSS-1_2x12_P2.00mm_Horizontal +Connector_JST:JST_PUD_S26B-PUDSS-1_2x13_P2.00mm_Horizontal +Connector_JST:JST_PUD_S28B-PUDSS-1_2x14_P2.00mm_Horizontal +Connector_JST:JST_PUD_S30B-PUDSS-1_2x15_P2.00mm_Horizontal +Connector_JST:JST_PUD_S32B-PUDSS-1_2x16_P2.00mm_Horizontal +Connector_JST:JST_PUD_S34B-PUDSS-1_2x17_P2.00mm_Horizontal +Connector_JST:JST_PUD_S36B-PUDSS-1_2x18_P2.00mm_Horizontal +Connector_JST:JST_PUD_S38B-PUDSS-1_2x19_P2.00mm_Horizontal +Connector_JST:JST_PUD_S40B-PUDSS-1_2x20_P2.00mm_Horizontal +Connector_JST:JST_SFH_SM02B-SFHRS-TF_1x02-1MP_P4.20mm_Horizontal +Connector_JST:JST_SHL_SM02B-SHLS-TF_1x02-1MP_P1.00mm_Horizontal +Connector_JST:JST_SHL_SM05B-SHLS-TF_1x05-1MP_P1.00mm_Horizontal +Connector_JST:JST_SHL_SM06B-SHLS-TF_1x06-1MP_P1.00mm_Horizontal +Connector_JST:JST_SHL_SM07B-SHLS-TF_1x07-1MP_P1.00mm_Horizontal +Connector_JST:JST_SHL_SM08B-SHLS-TF_1x08-1MP_P1.00mm_Horizontal +Connector_JST:JST_SHL_SM10B-SHLS-TF_1x10-1MP_P1.00mm_Horizontal +Connector_JST:JST_SHL_SM11B-SHLS-TF_1x11-1MP_P1.00mm_Horizontal +Connector_JST:JST_SHL_SM12B-SHLS-TF_1x12-1MP_P1.00mm_Horizontal +Connector_JST:JST_SHL_SM14B-SHLS-TF_1x14-1MP_P1.00mm_Horizontal +Connector_JST:JST_SHL_SM16B-SHLS-TF_1x16-1MP_P1.00mm_Horizontal +Connector_JST:JST_SHL_SM20B-SHLS-TF_1x20-1MP_P1.00mm_Horizontal +Connector_JST:JST_SHL_SM22B-SHLS-TF_1x22-1MP_P1.00mm_Horizontal +Connector_JST:JST_SHL_SM26B-SHLS-TF_1x26-1MP_P1.00mm_Horizontal +Connector_JST:JST_SHL_SM30B-SHLS-TF_1x30-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_BM02B-SRSS-TB_1x02-1MP_P1.00mm_Vertical +Connector_JST:JST_SH_BM03B-SRSS-TB_1x03-1MP_P1.00mm_Vertical +Connector_JST:JST_SH_BM04B-SRSS-TB_1x04-1MP_P1.00mm_Vertical +Connector_JST:JST_SH_BM05B-SRSS-TB_1x05-1MP_P1.00mm_Vertical +Connector_JST:JST_SH_BM06B-SRSS-TB_1x06-1MP_P1.00mm_Vertical +Connector_JST:JST_SH_BM07B-SRSS-TB_1x07-1MP_P1.00mm_Vertical +Connector_JST:JST_SH_BM08B-SRSS-TB_1x08-1MP_P1.00mm_Vertical +Connector_JST:JST_SH_BM09B-SRSS-TB_1x09-1MP_P1.00mm_Vertical +Connector_JST:JST_SH_BM10B-SRSS-TB_1x10-1MP_P1.00mm_Vertical +Connector_JST:JST_SH_BM11B-SRSS-TB_1x11-1MP_P1.00mm_Vertical +Connector_JST:JST_SH_BM12B-SRSS-TB_1x12-1MP_P1.00mm_Vertical +Connector_JST:JST_SH_BM13B-SRSS-TB_1x13-1MP_P1.00mm_Vertical +Connector_JST:JST_SH_BM14B-SRSS-TB_1x14-1MP_P1.00mm_Vertical +Connector_JST:JST_SH_BM15B-SRSS-TB_1x15-1MP_P1.00mm_Vertical +Connector_JST:JST_SH_SM02B-SRSS-TB_1x02-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_SM03B-SRSS-TB_1x03-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_SM04B-SRSS-TB_1x04-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_SM05B-SRSS-TB_1x05-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_SM06B-SRSS-TB_1x06-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_SM07B-SRSS-TB_1x07-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_SM08B-SRSS-TB_1x08-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_SM09B-SRSS-TB_1x09-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_SM10B-SRSS-TB_1x10-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_SM11B-SRSS-TB_1x11-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_SM12B-SRSS-TB_1x12-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_SM13B-SRSS-TB_1x13-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_SM14B-SRSS-TB_1x14-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_SM15B-SRSS-TB_1x15-1MP_P1.00mm_Horizontal +Connector_JST:JST_SH_SM20B-SRSS-TB_1x20-1MP_P1.00mm_Horizontal +Connector_JST:JST_SUR_BM02B-SURS-TF_1x02-1MP_P0.80mm_Vertical +Connector_JST:JST_SUR_BM03B-SURS-TF_1x03-1MP_P0.80mm_Vertical +Connector_JST:JST_SUR_BM04B-SURS-TF_1x04-1MP_P0.80mm_Vertical +Connector_JST:JST_SUR_BM05B-SURS-TF_1x05-1MP_P0.80mm_Vertical +Connector_JST:JST_SUR_BM06B-SURS-TF_1x06-1MP_P0.80mm_Vertical +Connector_JST:JST_SUR_BM08B-SURS-TF_1x08-1MP_P0.80mm_Vertical +Connector_JST:JST_SUR_BM10B-SURS-TF_1x10-1MP_P0.80mm_Vertical +Connector_JST:JST_SUR_BM12B-SURS-TF_1x12-1MP_P0.80mm_Vertical +Connector_JST:JST_SUR_BM14B-SURS-TF_1x14-1MP_P0.80mm_Vertical +Connector_JST:JST_SUR_BM15B-SURS-TF_1x15-1MP_P0.80mm_Vertical +Connector_JST:JST_SUR_BM16B-SURS-TF_1x16-1MP_P0.80mm_Vertical +Connector_JST:JST_SUR_BM17B-SURS-TF_1x17-1MP_P0.80mm_Vertical +Connector_JST:JST_SUR_BM20B-SURS-TF_1x20-1MP_P0.80mm_Vertical +Connector_JST:JST_SUR_SM02B-SURS-TF_1x02-1MP_P0.80mm_Horizontal +Connector_JST:JST_SUR_SM03B-SURS-TF_1x03-1MP_P0.80mm_Horizontal +Connector_JST:JST_SUR_SM04B-SURS-TF_1x04-1MP_P0.80mm_Horizontal +Connector_JST:JST_SUR_SM05B-SURS-TF_1x05-1MP_P0.80mm_Horizontal +Connector_JST:JST_SUR_SM06B-SURS-TF_1x06-1MP_P0.80mm_Horizontal +Connector_JST:JST_SUR_SM08B-SURS-TF_1x08-1MP_P0.80mm_Horizontal +Connector_JST:JST_SUR_SM10B-SURS-TF_1x10-1MP_P0.80mm_Horizontal +Connector_JST:JST_SUR_SM12B-SURS-TF_1x12-1MP_P0.80mm_Horizontal +Connector_JST:JST_SUR_SM14B-SURS-TF_1x14-1MP_P0.80mm_Horizontal +Connector_JST:JST_SUR_SM15B-SURS-TF_1x15-1MP_P0.80mm_Horizontal +Connector_JST:JST_SUR_SM16B-SURS-TF_1x16-1MP_P0.80mm_Horizontal +Connector_JST:JST_SUR_SM17B-SURS-TF_1x17-1MP_P0.80mm_Horizontal +Connector_JST:JST_SUR_SM20B-SURS-TF_1x20-1MP_P0.80mm_Horizontal +Connector_JST:JST_SUR_SM22B-SURS-TF_1x22-1MP_P0.80mm_Horizontal +Connector_JST:JST_VH_B10P-VH-B_1x10_P3.96mm_Vertical +Connector_JST:JST_VH_B10P-VH-FB-B_1x10_P3.96mm_Vertical +Connector_JST:JST_VH_B10P-VH_1x10_P3.96mm_Vertical +Connector_JST:JST_VH_B10PS-VH_1x10_P3.96mm_Horizontal +Connector_JST:JST_VH_B11P-VH-B_1x11_P3.96mm_Vertical +Connector_JST:JST_VH_B2P-VH-B_1x02_P3.96mm_Vertical +Connector_JST:JST_VH_B2P-VH-FB-B_1x02_P3.96mm_Vertical +Connector_JST:JST_VH_B2P-VH_1x02_P3.96mm_Vertical +Connector_JST:JST_VH_B2P3-VH_1x02_P7.92mm_Vertical +Connector_JST:JST_VH_B2PS-VH_1x02_P3.96mm_Horizontal +Connector_JST:JST_VH_B3P-VH-B_1x03_P3.96mm_Vertical +Connector_JST:JST_VH_B3P-VH-FB-B_1x03_P3.96mm_Vertical +Connector_JST:JST_VH_B3P-VH_1x03_P3.96mm_Vertical +Connector_JST:JST_VH_B3PS-VH_1x03_P3.96mm_Horizontal +Connector_JST:JST_VH_B4P-VH-B_1x04_P3.96mm_Vertical +Connector_JST:JST_VH_B4P-VH-FB-B_1x04_P3.96mm_Vertical +Connector_JST:JST_VH_B4P-VH_1x04_P3.96mm_Vertical +Connector_JST:JST_VH_B4PS-VH_1x04_P3.96mm_Horizontal +Connector_JST:JST_VH_B5P-VH-B_1x05_P3.96mm_Vertical +Connector_JST:JST_VH_B5P-VH-FB-B_1x05_P3.96mm_Vertical +Connector_JST:JST_VH_B5P-VH_1x05_P3.96mm_Vertical +Connector_JST:JST_VH_B5PS-VH_1x05_P3.96mm_Horizontal +Connector_JST:JST_VH_B6P-VH-B_1x06_P3.96mm_Vertical +Connector_JST:JST_VH_B6P-VH-FB-B_1x06_P3.96mm_Vertical +Connector_JST:JST_VH_B6P-VH_1x06_P3.96mm_Vertical +Connector_JST:JST_VH_B6PS-VH_1x06_P3.96mm_Horizontal +Connector_JST:JST_VH_B7P-VH-B_1x07_P3.96mm_Vertical +Connector_JST:JST_VH_B7P-VH-FB-B_1x07_P3.96mm_Vertical +Connector_JST:JST_VH_B7P-VH_1x07_P3.96mm_Vertical +Connector_JST:JST_VH_B7PS-VH_1x07_P3.96mm_Horizontal +Connector_JST:JST_VH_B8P-VH-B_1x08_P3.96mm_Vertical +Connector_JST:JST_VH_B8P-VH-FB-B_1x08_P3.96mm_Vertical +Connector_JST:JST_VH_B8P-VH_1x08_P3.96mm_Vertical +Connector_JST:JST_VH_B8PS-VH_1x08_P3.96mm_Horizontal +Connector_JST:JST_VH_B9P-VH-B_1x09_P3.96mm_Vertical +Connector_JST:JST_VH_B9P-VH-FB-B_1x09_P3.96mm_Vertical +Connector_JST:JST_VH_B9P-VH_1x09_P3.96mm_Vertical +Connector_JST:JST_VH_B9PS-VH_1x09_P3.96mm_Horizontal +Connector_JST:JST_VH_S2P-VH_1x02_P3.96mm_Horizontal +Connector_JST:JST_VH_S3P-VH_1x03_P3.96mm_Horizontal +Connector_JST:JST_VH_S4P-VH_1x04_P3.96mm_Horizontal +Connector_JST:JST_VH_S5P-VH_1x05_P3.96mm_Horizontal +Connector_JST:JST_VH_S6P-VH_1x06_P3.96mm_Horizontal +Connector_JST:JST_VH_S7P-VH_1x07_P3.96mm_Horizontal +Connector_JST:JST_XAG_SM05B-XAGKS-BN-TB_1x05-1MP_P2.50mm_Horizontal +Connector_JST:JST_XA_B02B-XASK-1-A_1x02_P2.50mm_Vertical +Connector_JST:JST_XA_B02B-XASK-1_1x02_P2.50mm_Vertical +Connector_JST:JST_XA_B03B-XASK-1-A_1x03_P2.50mm_Vertical +Connector_JST:JST_XA_B03B-XASK-1_1x03_P2.50mm_Vertical +Connector_JST:JST_XA_B04B-XASK-1-A_1x04_P2.50mm_Vertical +Connector_JST:JST_XA_B04B-XASK-1_1x04_P2.50mm_Vertical +Connector_JST:JST_XA_B05B-XASK-1-A_1x05_P2.50mm_Vertical +Connector_JST:JST_XA_B05B-XASK-1_1x05_P2.50mm_Vertical +Connector_JST:JST_XA_B06B-XASK-1-A_1x06_P2.50mm_Vertical +Connector_JST:JST_XA_B06B-XASK-1_1x06_P2.50mm_Vertical +Connector_JST:JST_XA_B07B-XASK-1-A_1x07_P2.50mm_Vertical +Connector_JST:JST_XA_B07B-XASK-1_1x07_P2.50mm_Vertical +Connector_JST:JST_XA_B08B-XASK-1-A_1x08_P2.50mm_Vertical +Connector_JST:JST_XA_B08B-XASK-1_1x08_P2.50mm_Vertical +Connector_JST:JST_XA_B09B-XASK-1-A_1x09_P2.50mm_Vertical +Connector_JST:JST_XA_B09B-XASK-1_1x09_P2.50mm_Vertical +Connector_JST:JST_XA_B10B-XASK-1-A_1x10_P2.50mm_Vertical +Connector_JST:JST_XA_B10B-XASK-1_1x10_P2.50mm_Vertical +Connector_JST:JST_XA_B11B-XASK-1-A_1x11_P2.50mm_Vertical +Connector_JST:JST_XA_B11B-XASK-1_1x11_P2.50mm_Vertical +Connector_JST:JST_XA_B12B-XASK-1-A_1x12_P2.50mm_Vertical +Connector_JST:JST_XA_B12B-XASK-1_1x12_P2.50mm_Vertical +Connector_JST:JST_XA_B13B-XASK-1-A_1x13_P2.50mm_Vertical +Connector_JST:JST_XA_B13B-XASK-1_1x13_P2.50mm_Vertical +Connector_JST:JST_XA_B14B-XASK-1-A_1x14_P2.50mm_Vertical +Connector_JST:JST_XA_B14B-XASK-1_1x14_P2.50mm_Vertical +Connector_JST:JST_XA_B15B-XASK-1-A_1x15_P2.50mm_Vertical +Connector_JST:JST_XA_B15B-XASK-1_1x15_P2.50mm_Vertical +Connector_JST:JST_XA_B18B-XASK-1_1x18_P2.50mm_Vertical +Connector_JST:JST_XA_B20B-XASK-1-A_1x20_P2.50mm_Vertical +Connector_JST:JST_XA_B20B-XASK-1_1x20_P2.50mm_Vertical +Connector_JST:JST_XA_S02B-XASK-1N-BN_1x02_P2.50mm_Horizontal +Connector_JST:JST_XA_S02B-XASK-1_1x02_P2.50mm_Horizontal +Connector_JST:JST_XA_S03B-XASK-1N-BN_1x03_P2.50mm_Horizontal +Connector_JST:JST_XA_S03B-XASK-1_1x03_P2.50mm_Horizontal +Connector_JST:JST_XA_S04B-XASK-1N-BN_1x04_P2.50mm_Horizontal +Connector_JST:JST_XA_S04B-XASK-1_1x04_P2.50mm_Horizontal +Connector_JST:JST_XA_S05B-XASK-1N-BN_1x05_P2.50mm_Horizontal +Connector_JST:JST_XA_S05B-XASK-1_1x05_P2.50mm_Horizontal +Connector_JST:JST_XA_S06B-XASK-1N-BN_1x06_P2.50mm_Horizontal +Connector_JST:JST_XA_S06B-XASK-1_1x06_P2.50mm_Horizontal +Connector_JST:JST_XA_S07B-XASK-1N-BN_1x07_P2.50mm_Horizontal +Connector_JST:JST_XA_S07B-XASK-1_1x07_P2.50mm_Horizontal +Connector_JST:JST_XA_S08B-XASK-1N-BN_1x08_P2.50mm_Horizontal +Connector_JST:JST_XA_S08B-XASK-1_1x08_P2.50mm_Horizontal +Connector_JST:JST_XA_S09B-XASK-1N-BN_1x09_P2.50mm_Horizontal +Connector_JST:JST_XA_S09B-XASK-1_1x09_P2.50mm_Horizontal +Connector_JST:JST_XA_S10B-XASK-1N-BN_1x10_P2.50mm_Horizontal +Connector_JST:JST_XA_S10B-XASK-1_1x10_P2.50mm_Horizontal +Connector_JST:JST_XA_S11B-XASK-1N-BN_1x11_P2.50mm_Horizontal +Connector_JST:JST_XA_S11B-XASK-1_1x11_P2.50mm_Horizontal +Connector_JST:JST_XA_S12B-XASK-1N-BN_1x12_P2.50mm_Horizontal +Connector_JST:JST_XA_S12B-XASK-1_1x12_P2.50mm_Horizontal +Connector_JST:JST_XA_S13B-XASK-1N-BN_1x13_P2.50mm_Horizontal +Connector_JST:JST_XA_S13B-XASK-1_1x13_P2.50mm_Horizontal +Connector_JST:JST_XA_S14B-XASK-1N-BN_1x14_P2.50mm_Horizontal +Connector_JST:JST_XA_S14B-XASK-1_1x14_P2.50mm_Horizontal +Connector_JST:JST_XH_B10B-XH-AM_1x10_P2.50mm_Vertical +Connector_JST:JST_XH_B10B-XH-A_1x10_P2.50mm_Vertical +Connector_JST:JST_XH_B11B-XH-A_1x11_P2.50mm_Vertical +Connector_JST:JST_XH_B12B-XH-AM_1x12_P2.50mm_Vertical +Connector_JST:JST_XH_B12B-XH-A_1x12_P2.50mm_Vertical +Connector_JST:JST_XH_B13B-XH-A_1x13_P2.50mm_Vertical +Connector_JST:JST_XH_B14B-XH-A_1x14_P2.50mm_Vertical +Connector_JST:JST_XH_B15B-XH-A_1x15_P2.50mm_Vertical +Connector_JST:JST_XH_B16B-XH-A_1x16_P2.50mm_Vertical +Connector_JST:JST_XH_B1B-XH-AM_1x01_P2.50mm_Vertical +Connector_JST:JST_XH_B20B-XH-A_1x20_P2.50mm_Vertical +Connector_JST:JST_XH_B2B-XH-AM_1x02_P2.50mm_Vertical +Connector_JST:JST_XH_B2B-XH-A_1x02_P2.50mm_Vertical +Connector_JST:JST_XH_B3B-XH-AM_1x03_P2.50mm_Vertical +Connector_JST:JST_XH_B3B-XH-A_1x03_P2.50mm_Vertical +Connector_JST:JST_XH_B4B-XH-AM_1x04_P2.50mm_Vertical +Connector_JST:JST_XH_B4B-XH-A_1x04_P2.50mm_Vertical +Connector_JST:JST_XH_B5B-XH-AM_1x05_P2.50mm_Vertical +Connector_JST:JST_XH_B5B-XH-A_1x05_P2.50mm_Vertical +Connector_JST:JST_XH_B6B-XH-AM_1x06_P2.50mm_Vertical +Connector_JST:JST_XH_B6B-XH-A_1x06_P2.50mm_Vertical +Connector_JST:JST_XH_B7B-XH-AM_1x07_P2.50mm_Vertical +Connector_JST:JST_XH_B7B-XH-A_1x07_P2.50mm_Vertical +Connector_JST:JST_XH_B8B-XH-AM_1x08_P2.50mm_Vertical +Connector_JST:JST_XH_B8B-XH-A_1x08_P2.50mm_Vertical +Connector_JST:JST_XH_B9B-XH-AM_1x09_P2.50mm_Vertical +Connector_JST:JST_XH_B9B-XH-A_1x09_P2.50mm_Vertical +Connector_JST:JST_XH_S10B-XH-A-1_1x10_P2.50mm_Horizontal +Connector_JST:JST_XH_S10B-XH-A_1x10_P2.50mm_Horizontal +Connector_JST:JST_XH_S11B-XH-A-1_1x11_P2.50mm_Horizontal +Connector_JST:JST_XH_S11B-XH-A_1x11_P2.50mm_Horizontal +Connector_JST:JST_XH_S12B-XH-A-1_1x12_P2.50mm_Horizontal +Connector_JST:JST_XH_S12B-XH-A_1x12_P2.50mm_Horizontal +Connector_JST:JST_XH_S13B-XH-A-1_1x13_P2.50mm_Horizontal +Connector_JST:JST_XH_S13B-XH-A_1x13_P2.50mm_Horizontal +Connector_JST:JST_XH_S14B-XH-A-1_1x14_P2.50mm_Horizontal +Connector_JST:JST_XH_S14B-XH-A_1x14_P2.50mm_Horizontal +Connector_JST:JST_XH_S15B-XH-A-1_1x15_P2.50mm_Horizontal +Connector_JST:JST_XH_S15B-XH-A_1x15_P2.50mm_Horizontal +Connector_JST:JST_XH_S16B-XH-A_1x16_P2.50mm_Horizontal +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 +Connector_JST:JST_XH_S8B-XH-A_1x08_P2.50mm_Horizontal +Connector_JST:JST_XH_S9B-XH-A-1_1x09_P2.50mm_Horizontal +Connector_JST:JST_XH_S9B-XH-A_1x09_P2.50mm_Horizontal +Connector_JST:JST_ZE_B02B-ZESK-1D_1x02_P1.50mm_Vertical +Connector_JST:JST_ZE_B03B-ZESK-1D_1x03_P1.50mm_Vertical +Connector_JST:JST_ZE_B03B-ZESK-D_1x03_P1.50mm_Vertical +Connector_JST:JST_ZE_B04B-ZESK-1D_1x04_P1.50mm_Vertical +Connector_JST:JST_ZE_B04B-ZESK-D_1x04_P1.50mm_Vertical +Connector_JST:JST_ZE_B05B-ZESK-1D_1x05_P1.50mm_Vertical +Connector_JST:JST_ZE_B05B-ZESK-D_1x05_P1.50mm_Vertical +Connector_JST:JST_ZE_B06B-ZESK-1D_1x06_P1.50mm_Vertical +Connector_JST:JST_ZE_B06B-ZESK-D_1x06_P1.50mm_Vertical +Connector_JST:JST_ZE_B07B-ZESK-1D_1x07_P1.50mm_Vertical +Connector_JST:JST_ZE_B07B-ZESK-D_1x07_P1.50mm_Vertical +Connector_JST:JST_ZE_B08B-ZESK-1D_1x08_P1.50mm_Vertical +Connector_JST:JST_ZE_B08B-ZESK-D_1x08_P1.50mm_Vertical +Connector_JST:JST_ZE_B09B-ZESK-1D_1x09_P1.50mm_Vertical +Connector_JST:JST_ZE_B09B-ZESK-D_1x09_P1.50mm_Vertical +Connector_JST:JST_ZE_B10B-ZESK-1D_1x10_P1.50mm_Vertical +Connector_JST:JST_ZE_B10B-ZESK-D_1x10_P1.50mm_Vertical +Connector_JST:JST_ZE_B11B-ZESK-1D_1x11_P1.50mm_Vertical +Connector_JST:JST_ZE_B11B-ZESK-D_1x11_P1.50mm_Vertical +Connector_JST:JST_ZE_B12B-ZESK-1D_1x12_P1.50mm_Vertical +Connector_JST:JST_ZE_B12B-ZESK-D_1x12_P1.50mm_Vertical +Connector_JST:JST_ZE_B13B-ZESK-1D_1x13_P1.50mm_Vertical +Connector_JST:JST_ZE_B13B-ZESK-D_1x13_P1.50mm_Vertical +Connector_JST:JST_ZE_B14B-ZESK-1D_1x14_P1.50mm_Vertical +Connector_JST:JST_ZE_B14B-ZESK-D_1x14_P1.50mm_Vertical +Connector_JST:JST_ZE_B15B-ZESK-1D_1x15_P1.50mm_Vertical +Connector_JST:JST_ZE_B15B-ZESK-D_1x15_P1.50mm_Vertical +Connector_JST:JST_ZE_B16B-ZESK-1D_1x16_P1.50mm_Vertical +Connector_JST:JST_ZE_B16B-ZESK-D_1x16_P1.50mm_Vertical +Connector_JST:JST_ZE_BM02B-ZESS-TBT_1x02-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_BM03B-ZESS-TBT_1x03-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_BM04B-ZESS-TBT_1x04-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_BM05B-ZESS-TBT_1x05-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_BM06B-ZESS-TBT_1x06-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_BM07B-ZESS-TBT_1x07-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_BM08B-ZESS-TBT_1x08-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_BM09B-ZESS-TBT_1x09-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_BM10B-ZESS-TBT_1x10-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_BM11B-ZESS-TBT_1x11-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_BM12B-ZESS-TBT_1x12-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_BM13B-ZESS-TBT_1x13-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_BM14B-ZESS-TBT_1x14-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_BM15B-ZESS-TBT_1x15-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_BM16B-ZESS-TBT_1x16-1MP_P1.50mm_Vertical +Connector_JST:JST_ZE_S02B-ZESK-2D_1x02_P1.50mm_Horizontal +Connector_JST:JST_ZE_S03B-ZESK-2D_1x03_P1.50mm_Horizontal +Connector_JST:JST_ZE_S04B-ZESK-2D_1x04_P1.50mm_Horizontal +Connector_JST:JST_ZE_S05B-ZESK-2D_1x05_P1.50mm_Horizontal +Connector_JST:JST_ZE_S06B-ZESK-2D_1x06_P1.50mm_Horizontal +Connector_JST:JST_ZE_S07B-ZESK-2D_1x07_P1.50mm_Horizontal +Connector_JST:JST_ZE_S08B-ZESK-2D_1x08_P1.50mm_Horizontal +Connector_JST:JST_ZE_S09B-ZESK-2D_1x09_P1.50mm_Horizontal +Connector_JST:JST_ZE_S10B-ZESK-2D_1x10_P1.50mm_Horizontal +Connector_JST:JST_ZE_S11B-ZESK-2D_1x11_P1.50mm_Horizontal +Connector_JST:JST_ZE_S12B-ZESK-2D_1x12_P1.50mm_Horizontal +Connector_JST:JST_ZE_S13B-ZESK-2D_1x13_P1.50mm_Horizontal +Connector_JST:JST_ZE_S14B-ZESK-2D_1x14_P1.50mm_Horizontal +Connector_JST:JST_ZE_S15B-ZESK-2D_1x15_P1.50mm_Horizontal +Connector_JST:JST_ZE_S16B-ZESK-2D_1x16_P1.50mm_Horizontal +Connector_JST:JST_ZE_SM02B-ZESS-TB_1x02-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZE_SM03B-ZESS-TB_1x03-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZE_SM04B-ZESS-TB_1x04-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZE_SM05B-ZESS-TB_1x05-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZE_SM06B-ZESS-TB_1x06-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZE_SM07B-ZESS-TB_1x07-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZE_SM08B-ZESS-TB_1x08-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZE_SM09B-ZESS-TB_1x09-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZE_SM10B-ZESS-TB_1x10-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZE_SM11B-ZESS-TB_1x11-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZE_SM12B-ZESS-TB_1x12-1MP_P1.50mm_Horizontal +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 +Connector_Molex:Molex_CLIK-Mate_502382-0570_1x05-1MP_P1.25mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502382-0670_1x06-1MP_P1.25mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502382-0770_1x07-1MP_P1.25mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502382-0870_1x08-1MP_P1.25mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502382-0970_1x09-1MP_P1.25mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502382-1070_1x10-1MP_P1.25mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502382-1170_1x11-1MP_P1.25mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502382-1270_1x12-1MP_P1.25mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502382-1370_1x13-1MP_P1.25mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502382-1470_1x14-1MP_P1.25mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502382-1570_1x15-1MP_P1.25mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502386-0270_1x02-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502386-0370_1x03-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502386-0470_1x04-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502386-0570_1x05-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502386-0670_1x06-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502386-0770_1x07-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502386-0870_1x08-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502386-0970_1x09-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502386-1070_1x10-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502386-1170_1x11-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502386-1270_1x12-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502386-1370_1x13-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502386-1470_1x14-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502386-1570_1x15-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502443-0270_1x02-1MP_P2.00mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502443-0370_1x03-1MP_P2.00mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502443-0470_1x04-1MP_P2.00mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502443-0570_1x05-1MP_P2.00mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502443-0670_1x06-1MP_P2.00mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502443-0770_1x07-1MP_P2.00mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502443-0870_1x08-1MP_P2.00mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502443-0970_1x09-1MP_P2.00mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502443-1270_1x12-1MP_P2.00mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502443-1370_1x13-1MP_P2.00mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502443-1470_1x14-1MP_P2.00mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502443-1570_1x15-1MP_P2.00mm_Vertical +Connector_Molex:Molex_CLIK-Mate_502494-0270_1x02-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502494-0370_1x03-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502494-0470_1x04-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502494-0670_1x06-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502494-0870_1x08-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502494-1070_1x10-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502494-1270_1x12-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502494-1370_1x13-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502494-1470_1x14-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502494-1570_1x15-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502585-0270_1x02-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502585-0370_1x03-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502585-0470_1x04-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502585-0570_1x05-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502585-0670_1x06-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502585-0770_1x07-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502585-0870_1x08-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502585-0970_1x09-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502585-1070_1x10-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502585-1170_1x11-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502585-1270_1x12-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502585-1370_1x13-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502585-1470_1x14-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_502585-1570_1x15-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_CLIK-Mate_505405-0270_1x02-1MP_P1.50mm_Vertical +Connector_Molex:Molex_CLIK-Mate_505405-0370_1x03-1MP_P1.50mm_Vertical +Connector_Molex:Molex_CLIK-Mate_505405-0470_1x04-1MP_P1.50mm_Vertical +Connector_Molex:Molex_CLIK-Mate_505405-0570_1x05-1MP_P1.50mm_Vertical +Connector_Molex:Molex_CLIK-Mate_505405-0670_1x06-1MP_P1.50mm_Vertical +Connector_Molex:Molex_CLIK-Mate_505405-0770_1x07-1MP_P1.50mm_Vertical +Connector_Molex:Molex_CLIK-Mate_505405-0870_1x08-1MP_P1.50mm_Vertical +Connector_Molex:Molex_CLIK-Mate_505405-0970_1x09-1MP_P1.50mm_Vertical +Connector_Molex:Molex_CLIK-Mate_505405-1070_1x10-1MP_P1.50mm_Vertical +Connector_Molex:Molex_CLIK-Mate_505405-1170_1x11-1MP_P1.50mm_Vertical +Connector_Molex:Molex_CLIK-Mate_505405-1270_1x12-1MP_P1.50mm_Vertical +Connector_Molex:Molex_CLIK-Mate_505405-1370_1x13-1MP_P1.50mm_Vertical +Connector_Molex:Molex_CLIK-Mate_505405-1470_1x14-1MP_P1.50mm_Vertical +Connector_Molex:Molex_CLIK-Mate_505405-1570_1x15-1MP_P1.50mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-02A_1x02_P2.54mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-03A_1x03_P2.54mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-04A_1x04_P2.54mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-05A_1x05_P2.54mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-06A_1x06_P2.54mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-07A_1x07_P2.54mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-08A_1x08_P2.54mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-09A_1x09_P2.54mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-10A_1x10_P2.54mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-11A_1x11_P2.54mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-12A_1x12_P2.54mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-13A_1x13_P2.54mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-14A_1x14_P2.54mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-15A_1x15_P2.54mm_Vertical +Connector_Molex:Molex_KK-254_AE-6410-16A_1x16_P2.54mm_Vertical +Connector_Molex:Molex_KK-396_5273-02A_1x02_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_5273-03A_1x03_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_5273-04A_1x04_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_5273-05A_1x05_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_5273-06A_1x06_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_5273-07A_1x07_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_5273-08A_1x08_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_5273-09A_1x09_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_5273-10A_1x10_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_5273-11A_1x11_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_5273-12A_1x12_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0002_1x02_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0003_1x03_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0004_1x04_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0005_1x05_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0006_1x06_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0007_1x07_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0008_1x08_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0009_1x09_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0010_1x10_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0011_1x11_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0012_1x12_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0013_1x13_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0014_1x14_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0015_1x15_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0016_1x16_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0017_1x17_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41791-0018_1x18_P3.96mm_Vertical +Connector_Molex:Molex_KK-396_A-41792-0002_1x02_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0003_1x03_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0004_1x04_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0005_1x05_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0006_1x06_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0007_1x07_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0008_1x08_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0009_1x09_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0010_1x10_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0011_1x11_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0012_1x12_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0013_1x13_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0014_1x14_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0015_1x15_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0016_1x16_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0017_1x17_P3.96mm_Horizontal +Connector_Molex:Molex_KK-396_A-41792-0018_1x18_P3.96mm_Horizontal +Connector_Molex:Molex_Mega-Fit_76825-0002_2x01_P5.70mm_Horizontal +Connector_Molex:Molex_Mega-Fit_76825-0004_2x02_P5.70mm_Horizontal +Connector_Molex:Molex_Mega-Fit_76825-0006_2x03_P5.70mm_Horizontal +Connector_Molex:Molex_Mega-Fit_76825-0008_2x04_P5.70mm_Horizontal +Connector_Molex:Molex_Mega-Fit_76825-0010_2x05_P5.70mm_Horizontal +Connector_Molex:Molex_Mega-Fit_76825-0012_2x06_P5.70mm_Horizontal +Connector_Molex:Molex_Mega-Fit_76829-0002_2x01_P5.70mm_Vertical +Connector_Molex:Molex_Mega-Fit_76829-0004_2x02_P5.70mm_Vertical +Connector_Molex:Molex_Mega-Fit_76829-0006_2x03_P5.70mm_Vertical +Connector_Molex:Molex_Mega-Fit_76829-0008_2x04_P5.70mm_Vertical +Connector_Molex:Molex_Mega-Fit_76829-0010_2x05_P5.70mm_Vertical +Connector_Molex:Molex_Mega-Fit_76829-0012_2x06_P5.70mm_Vertical +Connector_Molex:Molex_Mega-Fit_76829-0102_2x01_P5.70mm_Vertical +Connector_Molex:Molex_Mega-Fit_76829-0104_2x02_P5.70mm_Vertical +Connector_Molex:Molex_Mega-Fit_76829-0106_2x03_P5.70mm_Vertical +Connector_Molex:Molex_Mega-Fit_76829-0108_2x04_P5.70mm_Vertical +Connector_Molex:Molex_Mega-Fit_76829-0110_2x05_P5.70mm_Vertical +Connector_Molex:Molex_Mega-Fit_76829-0112_2x06_P5.70mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-0200_2x01_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-0210_2x01-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-0212_2x01_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-0215_2x01_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-0218_2x01-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-0221_2x01-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-0400_2x02_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-0410_2x02-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-0412_2x02_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-0415_2x02_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-0418_2x02-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-0421_2x02-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-0600_2x03_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-0610_2x03-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-0612_2x03_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-0615_2x03_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-0618_2x03-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-0621_2x03-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-0800_2x04_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-0810_2x04-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-0812_2x04_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-0815_2x04_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-0818_2x04-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-0821_2x04-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1000_2x05_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1010_2x05-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1012_2x05_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1015_2x05_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1018_2x05-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1021_2x05-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1200_2x06_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1210_2x06-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1212_2x06_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1215_2x06_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1218_2x06-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1221_2x06-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1400_2x07_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1410_2x07-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1412_2x07_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1415_2x07_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1418_2x07-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1421_2x07-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1600_2x08_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1610_2x08-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1612_2x08_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1615_2x08_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1618_2x08-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1621_2x08-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1800_2x09_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1810_2x09-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-1812_2x09_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1815_2x09_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1818_2x09-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-1821_2x09-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-2000_2x10_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-2010_2x10-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-2012_2x10_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-2015_2x10_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-2018_2x10-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-2021_2x10-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-2200_2x11_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-2210_2x11-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-2212_2x11_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-2215_2x11_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-2218_2x11-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-2221_2x11-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-2400_2x12_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-2410_2x12-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43045-2412_2x12_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-2415_2x12_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-2418_2x12-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43045-2421_2x12-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0200_1x02_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0210_1x02-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0210_1x02-1MP_P3.00mm_Horizontal_PnP +Connector_Molex:Molex_Micro-Fit_3.0_43650-0215_1x02_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0221_1x02_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0224_1x02-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0300_1x03_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0310_1x03-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0310_1x03-1MP_P3.00mm_Horizontal_PnP +Connector_Molex:Molex_Micro-Fit_3.0_43650-0315_1x03_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0321_1x03_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0324_1x03-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0400_1x04_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0410_1x04-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0410_1x04-1MP_P3.00mm_Horizontal_PnP +Connector_Molex:Molex_Micro-Fit_3.0_43650-0415_1x04_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0421_1x04_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0424_1x04-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0500_1x05_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0510_1x05-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0510_1x05-1MP_P3.00mm_Horizontal_PnP +Connector_Molex:Molex_Micro-Fit_3.0_43650-0515_1x05_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0521_1x05_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0524_1x05-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0600_1x06_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0610_1x06-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0610_1x06-1MP_P3.00mm_Horizontal_PnP +Connector_Molex:Molex_Micro-Fit_3.0_43650-0615_1x06_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0621_1x06_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0624_1x06-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0700_1x07_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0710_1x07-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0710_1x07-1MP_P3.00mm_Horizontal_PnP +Connector_Molex:Molex_Micro-Fit_3.0_43650-0715_1x07_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0721_1x07_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0724_1x07-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0800_1x08_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0810_1x08-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0810_1x08-1MP_P3.00mm_Horizontal_PnP +Connector_Molex:Molex_Micro-Fit_3.0_43650-0815_1x08_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0821_1x08_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0824_1x08-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0900_1x09_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0910_1x09-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-0910_1x09-1MP_P3.00mm_Horizontal_PnP +Connector_Molex:Molex_Micro-Fit_3.0_43650-0915_1x09_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0921_1x09_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-0924_1x09-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-1000_1x10_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-1010_1x10-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-1010_1x10-1MP_P3.00mm_Horizontal_PnP +Connector_Molex:Molex_Micro-Fit_3.0_43650-1015_1x10_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-1021_1x10_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-1024_1x10-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-1100_1x11_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-1110_1x11-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-1110_1x11-1MP_P3.00mm_Horizontal_PnP +Connector_Molex:Molex_Micro-Fit_3.0_43650-1115_1x11_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-1121_1x11_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-1124_1x11-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-1200_1x12_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-1210_1x12-1MP_P3.00mm_Horizontal +Connector_Molex:Molex_Micro-Fit_3.0_43650-1210_1x12-1MP_P3.00mm_Horizontal_PnP +Connector_Molex:Molex_Micro-Fit_3.0_43650-1215_1x12_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-1221_1x12_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Fit_3.0_43650-1224_1x12-1MP_P3.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53253-0270_1x02_P2.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53253-0370_1x03_P2.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53253-0470_1x04_P2.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53253-0570_1x05_P2.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53253-0670_1x06_P2.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53253-0770_1x07_P2.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53253-0870_1x08_P2.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53253-0970_1x09_P2.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53253-1070_1x10_P2.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53253-1170_1x11_P2.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53253-1270_1x12_P2.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53253-1370_1x13_P2.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53253-1470_1x14_P2.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53253-1570_1x15_P2.00mm_Vertical +Connector_Molex:Molex_Micro-Latch_53254-0270_1x02_P2.00mm_Horizontal +Connector_Molex:Molex_Micro-Latch_53254-0370_1x03_P2.00mm_Horizontal +Connector_Molex:Molex_Micro-Latch_53254-0470_1x04_P2.00mm_Horizontal +Connector_Molex:Molex_Micro-Latch_53254-0570_1x05_P2.00mm_Horizontal +Connector_Molex:Molex_Micro-Latch_53254-0670_1x06_P2.00mm_Horizontal +Connector_Molex:Molex_Micro-Latch_53254-0770_1x07_P2.00mm_Horizontal +Connector_Molex:Molex_Micro-Latch_53254-0870_1x08_P2.00mm_Horizontal +Connector_Molex:Molex_Micro-Latch_53254-0970_1x09_P2.00mm_Horizontal +Connector_Molex:Molex_Micro-Latch_53254-1070_1x10_P2.00mm_Horizontal +Connector_Molex:Molex_Micro-Latch_53254-1170_1x11_P2.00mm_Horizontal +Connector_Molex:Molex_Micro-Latch_53254-1270_1x12_P2.00mm_Horizontal +Connector_Molex:Molex_Micro-Latch_53254-1370_1x13_P2.00mm_Horizontal +Connector_Molex:Molex_Micro-Latch_53254-1470_1x14_P2.00mm_Horizontal +Connector_Molex:Molex_Micro-Latch_53254-1570_1x15_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55932-0210_1x02_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0230_1x02_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0310_1x03_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0330_1x03_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0410_1x04_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0430_1x04_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0510_1x05_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0530_1x05_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0610_1x06_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0630_1x06_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0710_1x07_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0730_1x07_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0810_1x08_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0830_1x08_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0910_1x09_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-0930_1x09_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-1010_1x10_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-1030_1x10_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-1110_1x11_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-1130_1x11_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-1210_1x12_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-1230_1x12_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-1310_1x13_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-1330_1x13_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-1410_1x14_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-1430_1x14_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-1510_1x15_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55932-1530_1x15_P2.00mm_Vertical +Connector_Molex:Molex_MicroClasp_55935-0210_1x02_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0230_1x02_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0310_1x03_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0330_1x03_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0410_1x04_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0430_1x04_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0510_1x05_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0530_1x05_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0610_1x06_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0630_1x06_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0710_1x07_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0730_1x07_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0810_1x08_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0830_1x08_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0910_1x09_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-0930_1x09_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-1010_1x10_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-1030_1x10_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-1110_1x11_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-1130_1x11_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-1210_1x12_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-1230_1x12_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-1310_1x13_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-1330_1x13_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-1410_1x14_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-1430_1x14_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-1510_1x15_P2.00mm_Horizontal +Connector_Molex:Molex_MicroClasp_55935-1530_1x15_P2.00mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5566-02A2_2x01_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-02A_2x01_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-04A2_2x02_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-04A_2x02_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-06A2_2x03_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-06A_2x03_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-08A2_2x04_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-08A_2x04_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-10A2_2x05_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-10A_2x05_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-12A2_2x06_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-12A_2x06_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-14A2_2x07_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-14A_2x07_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-16A2_2x08_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-16A_2x08_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-18A2_2x09_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-18A_2x09_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-20A2_2x10_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-20A_2x10_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-22A2_2x11_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-22A_2x11_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-24A2_2x12_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5566-24A_2x12_P4.20mm_Vertical +Connector_Molex:Molex_Mini-Fit_Jr_5569-02A1_2x01_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-02A2_2x01_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-04A1_2x02_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-04A2_2x02_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-06A1_2x03_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-06A2_2x03_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-08A1_2x04_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-08A2_2x04_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-10A1_2x05_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-10A2_2x05_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-12A1_2x06_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-12A2_2x06_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-14A1_2x07_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-14A2_2x07_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-16A1_2x08_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-16A2_2x08_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-18A1_2x09_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-18A2_2x09_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-20A1_2x10_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-20A2_2x10_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-22A1_2x11_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-22A2_2x11_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-24A1_2x12_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Jr_5569-24A2_2x12_P4.20mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Sr_42819-22XX_1x02_P10.00mm_Vertical +Connector_Molex:Molex_Mini-Fit_Sr_42819-22XX_1x02_P10.00mm_Vertical_ThermalVias +Connector_Molex:Molex_Mini-Fit_Sr_42819-32XX_1x03_P10.00mm_Vertical +Connector_Molex:Molex_Mini-Fit_Sr_42819-32XX_1x03_P10.00mm_Vertical_ThermalVias +Connector_Molex:Molex_Mini-Fit_Sr_42819-42XX_1x04_P10.00mm_Vertical +Connector_Molex:Molex_Mini-Fit_Sr_42819-42XX_1x04_P10.00mm_Vertical_ThermalVias +Connector_Molex:Molex_Mini-Fit_Sr_42819-52XX_1x05_P10.00mm_Vertical +Connector_Molex:Molex_Mini-Fit_Sr_42819-52XX_1x05_P10.00mm_Vertical_ThermalVias +Connector_Molex:Molex_Mini-Fit_Sr_42819-62XX_1x06_P10.00mm_Vertical +Connector_Molex:Molex_Mini-Fit_Sr_42819-62XX_1x06_P10.00mm_Vertical_ThermalVias +Connector_Molex:Molex_Mini-Fit_Sr_42820-22XX_1x02_P10.00mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Sr_42820-22XX_1x02_P10.00mm_Horizontal_ThermalVias +Connector_Molex:Molex_Mini-Fit_Sr_42820-32XX_1x03_P10.00mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Sr_42820-32XX_1x03_P10.00mm_Horizontal_ThermalVias +Connector_Molex:Molex_Mini-Fit_Sr_42820-42XX_1x04_P10.00mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Sr_42820-42XX_1x04_P10.00mm_Horizontal_ThermalVias +Connector_Molex:Molex_Mini-Fit_Sr_42820-52XX_1x05_P10.00mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Sr_42820-52XX_1x05_P10.00mm_Horizontal_ThermalVias +Connector_Molex:Molex_Mini-Fit_Sr_42820-62XX_1x06_P10.00mm_Horizontal +Connector_Molex:Molex_Mini-Fit_Sr_42820-62XX_1x06_P10.00mm_Horizontal_ThermalVias +Connector_Molex:Molex_Mini-Fit_Sr_43915-xx06_2x03_P10.00mm_Vertical +Connector_Molex:Molex_Mini-Fit_Sr_43915-xx06_2x03_P10.00mm_Vertical_ThermalVias +Connector_Molex:Molex_Mini-Fit_Sr_43915-xx08_2x04_P10.00mm_Vertical +Connector_Molex:Molex_Mini-Fit_Sr_43915-xx08_2x04_P10.00mm_Vertical_ThermalVias +Connector_Molex:Molex_Mini-Fit_Sr_43915-xx10_2x05_P10.00mm_Vertical +Connector_Molex:Molex_Mini-Fit_Sr_43915-xx10_2x05_P10.00mm_Vertical_ThermalVias +Connector_Molex:Molex_Mini-Fit_Sr_43915-xx12_2x06_P10.00mm_Vertical +Connector_Molex:Molex_Mini-Fit_Sr_43915-xx12_2x06_P10.00mm_Vertical_ThermalVias +Connector_Molex:Molex_Mini-Fit_Sr_43915-xx14_2x07_P10.00mm_Vertical +Connector_Molex:Molex_Mini-Fit_Sr_43915-xx14_2x07_P10.00mm_Vertical_ThermalVias +Connector_Molex:Molex_Nano-Fit_105309-xx02_1x02_P2.50mm_Vertical +Connector_Molex:Molex_Nano-Fit_105309-xx03_1x03_P2.50mm_Vertical +Connector_Molex:Molex_Nano-Fit_105309-xx04_1x04_P2.50mm_Vertical +Connector_Molex:Molex_Nano-Fit_105309-xx05_1x05_P2.50mm_Vertical +Connector_Molex:Molex_Nano-Fit_105309-xx06_1x06_P2.50mm_Vertical +Connector_Molex:Molex_Nano-Fit_105309-xx07_1x07_P2.50mm_Vertical +Connector_Molex:Molex_Nano-Fit_105309-xx08_1x08_P2.50mm_Vertical +Connector_Molex:Molex_Nano-Fit_105310-xx04_2x02_P2.50mm_Vertical +Connector_Molex:Molex_Nano-Fit_105310-xx06_2x03_P2.50mm_Vertical +Connector_Molex:Molex_Nano-Fit_105310-xx08_2x04_P2.50mm_Vertical +Connector_Molex:Molex_Nano-Fit_105310-xx10_2x05_P2.50mm_Vertical +Connector_Molex:Molex_Nano-Fit_105310-xx12_2x06_P2.50mm_Vertical +Connector_Molex:Molex_Nano-Fit_105310-xx14_2x07_P2.50mm_Vertical +Connector_Molex:Molex_Nano-Fit_105310-xx16_2x08_P2.50mm_Vertical +Connector_Molex:Molex_Nano-Fit_105313-xx02_1x02_P2.50mm_Horizontal +Connector_Molex:Molex_Nano-Fit_105313-xx03_1x03_P2.50mm_Horizontal +Connector_Molex:Molex_Nano-Fit_105313-xx04_1x04_P2.50mm_Horizontal +Connector_Molex:Molex_Nano-Fit_105313-xx05_1x05_P2.50mm_Horizontal +Connector_Molex:Molex_Nano-Fit_105313-xx06_1x06_P2.50mm_Horizontal +Connector_Molex:Molex_Nano-Fit_105313-xx07_1x07_P2.50mm_Horizontal +Connector_Molex:Molex_Nano-Fit_105313-xx08_1x08_P2.50mm_Horizontal +Connector_Molex:Molex_Nano-Fit_105314-xx04_2x02_P2.50mm_Horizontal +Connector_Molex:Molex_Nano-Fit_105314-xx06_2x03_P2.50mm_Horizontal +Connector_Molex:Molex_Nano-Fit_105314-xx08_2x04_P2.50mm_Horizontal +Connector_Molex:Molex_Nano-Fit_105314-xx10_2x05_P2.50mm_Horizontal +Connector_Molex:Molex_Nano-Fit_105314-xx12_2x06_P2.50mm_Horizontal +Connector_Molex:Molex_Nano-Fit_105314-xx14_2x07_P2.50mm_Horizontal +Connector_Molex:Molex_Nano-Fit_105314-xx16_2x08_P2.50mm_Horizontal +Connector_Molex:Molex_Panelmate_53780-0270_1x02-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_Panelmate_53780-0370_1x03-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_Panelmate_53780-0470_1x04-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_Panelmate_53780-0570_1x05-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_Panelmate_53780-0670_1x06-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_Panelmate_53780-0770_1x07-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_Panelmate_53780-0870_1x08-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_Panelmate_53780-0970_1x09-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_Panelmate_53780-1070_1x10-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_Panelmate_53780-1270_1x12-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_Panelmate_53780-1470_1x14-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_Panelmate_53780-1570_1x15-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_Panelmate_53780-1870_1x18-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_Panelmate_53780-3070_1x30-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_202396-0207_1x02-1MP_P1.00mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_202396-0307_1x03-1MP_P1.00mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_202396-0407_1x04-1MP_P1.00mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_202396-0507_1x05-1MP_P1.00mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_202396-0607_1x06-1MP_P1.00mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_202396-0707_1x07-1MP_P1.00mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_202396-0807_1x08-1MP_P1.00mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_202396-0907_1x09-1MP_P1.00mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_202396-1007_1x10-1MP_P1.00mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_202396-1107_1x11-1MP_P1.00mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_202396-1207_1x12-1MP_P1.00mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_202396-1307_1x13-1MP_P1.00mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_202396-1407_1x14-1MP_P1.00mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_202396-1507_1x15-1MP_P1.00mm_Horizontal +Connector_Molex:Molex_Pico-Clasp_501331-0207_1x02-1MP_P1.00mm_Vertical +Connector_Molex:Molex_Pico-Clasp_501331-0307_1x03-1MP_P1.00mm_Vertical +Connector_Molex:Molex_Pico-Clasp_501331-0407_1x04-1MP_P1.00mm_Vertical +Connector_Molex:Molex_Pico-Clasp_501331-0507_1x05-1MP_P1.00mm_Vertical +Connector_Molex:Molex_Pico-Clasp_501331-0607_1x06-1MP_P1.00mm_Vertical +Connector_Molex:Molex_Pico-Clasp_501331-0707_1x07-1MP_P1.00mm_Vertical +Connector_Molex:Molex_Pico-Clasp_501331-0807_1x08-1MP_P1.00mm_Vertical +Connector_Molex:Molex_Pico-Clasp_501331-0907_1x09-1MP_P1.00mm_Vertical +Connector_Molex:Molex_Pico-Clasp_501331-1007_1x10-1MP_P1.00mm_Vertical +Connector_Molex:Molex_Pico-Clasp_501331-1107_1x11-1MP_P1.00mm_Vertical +Connector_Molex:Molex_Pico-Clasp_501331-1207_1x12-1MP_P1.00mm_Vertical +Connector_Molex:Molex_Pico-Clasp_501331-1307_1x13-1MP_P1.00mm_Vertical +Connector_Molex:Molex_Pico-Clasp_501331-1407_1x14-1MP_P1.00mm_Vertical +Connector_Molex:Molex_Pico-Clasp_501331-1507_1x15-1MP_P1.00mm_Vertical +Connector_Molex:Molex_Pico-EZmate_78171-0002_1x02-1MP_P1.20mm_Vertical +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 +Connector_Molex:Molex_PicoBlade_53047-0310_1x03_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53047-0410_1x04_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53047-0510_1x05_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53047-0610_1x06_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53047-0710_1x07_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53047-0810_1x08_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53047-0910_1x09_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53047-1010_1x10_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53047-1110_1x11_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53047-1210_1x12_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53047-1310_1x13_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53047-1410_1x14_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53047-1510_1x15_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53048-0210_1x02_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53048-0310_1x03_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53048-0410_1x04_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53048-0510_1x05_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53048-0610_1x06_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53048-0710_1x07_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53048-0810_1x08_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53048-0910_1x09_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53048-1010_1x10_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53048-1110_1x11_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53048-1210_1x12_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53048-1310_1x13_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53048-1410_1x14_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53048-1510_1x15_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-0271_1x02-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-0371_1x03-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-0471_1x04-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-0571_1x05-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-0671_1x06-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-0771_1x07-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-0871_1x08-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-0971_1x09-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-1071_1x10-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-1171_1x11-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-1271_1x12-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-1371_1x13-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-1471_1x14-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-1571_1x15-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53261-1771_1x17-1MP_P1.25mm_Horizontal +Connector_Molex:Molex_PicoBlade_53398-0271_1x02-1MP_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53398-0371_1x03-1MP_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53398-0471_1x04-1MP_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53398-0571_1x05-1MP_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53398-0671_1x06-1MP_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53398-0771_1x07-1MP_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53398-0871_1x08-1MP_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53398-0971_1x09-1MP_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53398-1071_1x10-1MP_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53398-1171_1x11-1MP_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53398-1271_1x12-1MP_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53398-1371_1x13-1MP_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53398-1471_1x14-1MP_P1.25mm_Vertical +Connector_Molex:Molex_PicoBlade_53398-1571_1x15-1MP_P1.25mm_Vertical +Connector_Molex:Molex_Picoflex_90325-0004_2x02_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90325-0006_2x03_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90325-0008_2x04_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90325-0010_2x05_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90325-0012_2x06_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90325-0014_2x07_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90325-0016_2x08_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90325-0018_2x09_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90325-0020_2x10_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90325-0022_2x11_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90325-0024_2x12_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90325-0026_2x13_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90814-0004_2x02_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90814-0006_2x03_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90814-0008_2x04_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90814-0010_2x05_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90814-0012_2x06_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90814-0014_2x07_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90814-0016_2x08_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90814-0018_2x09_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90814-0020_2x10_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90814-0022_2x11_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90814-0024_2x12_P1.27mm_Vertical +Connector_Molex:Molex_Picoflex_90814-0026_2x13_P1.27mm_Vertical +Connector_Molex:Molex_Sabre_43160-0102_1x02_P7.49mm_Vertical +Connector_Molex:Molex_Sabre_43160-0102_1x02_P7.49mm_Vertical_ThermalVias +Connector_Molex:Molex_Sabre_43160-0103_1x03_P7.49mm_Vertical +Connector_Molex:Molex_Sabre_43160-0103_1x03_P7.49mm_Vertical_ThermalVias +Connector_Molex:Molex_Sabre_43160-0104_1x04_P7.49mm_Vertical +Connector_Molex:Molex_Sabre_43160-0104_1x04_P7.49mm_Vertical_ThermalVias +Connector_Molex:Molex_Sabre_43160-0105_1x05_P7.49mm_Vertical +Connector_Molex:Molex_Sabre_43160-0105_1x05_P7.49mm_Vertical_ThermalVias +Connector_Molex:Molex_Sabre_43160-0106_1x06_P7.49mm_Vertical +Connector_Molex:Molex_Sabre_43160-0106_1x06_P7.49mm_Vertical_ThermalVias +Connector_Molex:Molex_Sabre_43160-1102_1x02_P7.49mm_Horizontal +Connector_Molex:Molex_Sabre_43160-1102_1x02_P7.49mm_Horizontal_ThermalVias +Connector_Molex:Molex_Sabre_43160-1103_1x03_P7.49mm_Horizontal +Connector_Molex:Molex_Sabre_43160-1103_1x03_P7.49mm_Horizontal_ThermalVias +Connector_Molex:Molex_Sabre_43160-1104_1x04_P7.49mm_Horizontal +Connector_Molex:Molex_Sabre_43160-1104_1x04_P7.49mm_Horizontal_ThermalVias +Connector_Molex:Molex_Sabre_43160-1105_1x05_P7.49mm_Horizontal +Connector_Molex:Molex_Sabre_43160-1105_1x05_P7.49mm_Horizontal_ThermalVias +Connector_Molex:Molex_Sabre_43160-1106_1x06_P7.49mm_Horizontal +Connector_Molex:Molex_Sabre_43160-1106_1x06_P7.49mm_Horizontal_ThermalVias +Connector_Molex:Molex_Sabre_43160-2102_1x02_P7.49mm_Vertical +Connector_Molex:Molex_Sabre_43160-2102_1x02_P7.49mm_Vertical_ThermalVias +Connector_Molex:Molex_Sabre_43160-2103_1x03_P7.49mm_Vertical +Connector_Molex:Molex_Sabre_43160-2103_1x03_P7.49mm_Vertical_ThermalVias +Connector_Molex:Molex_Sabre_43160-2104_1x04_P7.49mm_Vertical +Connector_Molex:Molex_Sabre_43160-2104_1x04_P7.49mm_Vertical_ThermalVias +Connector_Molex:Molex_Sabre_43160-2105_1x05_P7.49mm_Vertical +Connector_Molex:Molex_Sabre_43160-2105_1x05_P7.49mm_Vertical_ThermalVias +Connector_Molex:Molex_Sabre_43160-2106_1x06_P7.49mm_Vertical +Connector_Molex:Molex_Sabre_43160-2106_1x06_P7.49mm_Vertical_ThermalVias +Connector_Molex:Molex_Sabre_46007-1102_1x02_P7.49mm_Horizontal +Connector_Molex:Molex_Sabre_46007-1102_1x02_P7.49mm_Horizontal_ThermalVias +Connector_Molex:Molex_Sabre_46007-1103_1x03_P7.49mm_Horizontal +Connector_Molex:Molex_Sabre_46007-1103_1x03_P7.49mm_Horizontal_ThermalVias +Connector_Molex:Molex_Sabre_46007-1104_1x04_P7.49mm_Horizontal +Connector_Molex:Molex_Sabre_46007-1104_1x04_P7.49mm_Horizontal_ThermalVias +Connector_Molex:Molex_Sabre_46007-1105_1x05_P7.49mm_Horizontal +Connector_Molex:Molex_Sabre_46007-1105_1x05_P7.49mm_Horizontal_ThermalVias +Connector_Molex:Molex_Sabre_46007-1106_1x06_P7.49mm_Horizontal +Connector_Molex:Molex_Sabre_46007-1106_1x06_P7.49mm_Horizontal_ThermalVias +Connector_Molex:Molex_SlimStack_501920-3001_2x15_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_501920-4001_2x20_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_501920-5001_2x25_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_502426-0810_2x04_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502426-1410_2x07_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502426-2010_2x10_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502426-2210_2x11_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502426-2410_2x12_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502426-2610_2x13_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502426-3010_2x15_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502426-3210_2x16_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502426-3410_2x17_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502426-4010_2x20_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502426-4410_2x22_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502426-5010_2x25_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502426-6010_2x30_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502426-6410_2x32_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502426-8010_2x40_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-0820_2x04_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-1410_2x07_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-2010_2x10_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-2210_2x11_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-2410_2x12_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-2610_2x13_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-3010_2x15_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-3210_2x16_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-3410_2x17_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-4010_2x20_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-4410_2x22_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-5010_2x25_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-6010_2x30_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-6410_2x32_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_502430-8010_2x40_P0.40mm_Vertical +Connector_Molex:Molex_SlimStack_52991-0208_2x10_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_52991-0308_2x15_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_52991-0408_2x20_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_52991-0508_2x25_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_52991-0608_2x30_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_52991-0708_2x35_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_52991-0808_2x40_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_53748-0208_2x10_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_53748-0308_2x15_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_53748-0408_2x20_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_53748-0608_2x30_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_53748-0708_2x35_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_53748-0808_2x40_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_54722-0164_2x08_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_54722-0204_2x10_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_54722-0224_2x11_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_54722-0244_2x12_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_54722-0304_2x15_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_54722-0344_2x17_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_54722-0404_2x20_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_54722-0504_2x25_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_54722-0604_2x30_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_54722-0804_2x40_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_55560-0161_2x08_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_55560-0201_2x10_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_55560-0221_2x11_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_55560-0241_2x12_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_55560-0301_2x15_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_55560-0341_2x17_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_55560-0401_2x20_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_55560-0501_2x25_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_55560-0601_2x30_P0.50mm_Vertical +Connector_Molex:Molex_SlimStack_55560-0801_2x40_P0.50mm_Vertical +Connector_Molex:Molex_SL_171971-0002_1x02_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0003_1x03_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0004_1x04_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0005_1x05_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0006_1x06_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0007_1x07_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0008_1x08_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0009_1x09_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0010_1x10_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0011_1x11_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0012_1x12_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0013_1x13_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0014_1x14_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0015_1x15_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0016_1x16_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0017_1x17_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0018_1x18_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0019_1x19_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0020_1x20_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0021_1x21_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0022_1x22_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0023_1x23_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0024_1x24_P2.54mm_Vertical +Connector_Molex:Molex_SL_171971-0025_1x25_P2.54mm_Vertical +Connector_Molex:Molex_SPOX_5267-02A_1x02_P2.50mm_Vertical +Connector_Molex:Molex_SPOX_5267-03A_1x03_P2.50mm_Vertical +Connector_Molex:Molex_SPOX_5267-04A_1x04_P2.50mm_Vertical +Connector_Molex:Molex_SPOX_5267-05A_1x05_P2.50mm_Vertical +Connector_Molex:Molex_SPOX_5267-06A_1x06_P2.50mm_Vertical +Connector_Molex:Molex_SPOX_5267-07A_1x07_P2.50mm_Vertical +Connector_Molex:Molex_SPOX_5267-08A_1x08_P2.50mm_Vertical +Connector_Molex:Molex_SPOX_5267-09A_1x09_P2.50mm_Vertical +Connector_Molex:Molex_SPOX_5267-10A_1x10_P2.50mm_Vertical +Connector_Molex:Molex_SPOX_5267-11A_1x11_P2.50mm_Vertical +Connector_Molex:Molex_SPOX_5267-12A_1x12_P2.50mm_Vertical +Connector_Molex:Molex_SPOX_5267-13A_1x13_P2.50mm_Vertical +Connector_Molex:Molex_SPOX_5267-14A_1x14_P2.50mm_Vertical +Connector_Molex:Molex_SPOX_5267-15A_1x15_P2.50mm_Vertical +Connector_Molex:Molex_SPOX_5268-02A_1x02_P2.50mm_Horizontal +Connector_Molex:Molex_SPOX_5268-03A_1x03_P2.50mm_Horizontal +Connector_Molex:Molex_SPOX_5268-04A_1x04_P2.50mm_Horizontal +Connector_Molex:Molex_SPOX_5268-05A_1x05_P2.50mm_Horizontal +Connector_Molex:Molex_SPOX_5268-06A_1x06_P2.50mm_Horizontal +Connector_Molex:Molex_SPOX_5268-07A_1x07_P2.50mm_Horizontal +Connector_Molex:Molex_SPOX_5268-08A_1x08_P2.50mm_Horizontal +Connector_Molex:Molex_SPOX_5268-09A_1x09_P2.50mm_Horizontal +Connector_Molex:Molex_SPOX_5268-10A_1x10_P2.50mm_Horizontal +Connector_Molex:Molex_SPOX_5268-11A_1x11_P2.50mm_Horizontal +Connector_Molex:Molex_SPOX_5268-12A_1x12_P2.50mm_Horizontal +Connector_Molex:Molex_SPOX_5268-13A_1x13_P2.50mm_Horizontal +Connector_Molex:Molex_SPOX_5268-14A_1x14_P2.50mm_Horizontal +Connector_Molex:Molex_SPOX_5268-15A_1x15_P2.50mm_Horizontal +Connector_PCBEdge:4UCON_10156_2x40_P1.27mm_Socket_Horizontal +Connector_PCBEdge:BUS_AT +Connector_PCBEdge:BUS_PCI +Connector_PCBEdge:BUS_PCIexpress_x1 +Connector_PCBEdge:BUS_PCIexpress_x16 +Connector_PCBEdge:BUS_PCIexpress_x4 +Connector_PCBEdge:BUS_PCIexpress_x8 +Connector_PCBEdge:BUS_PCI_Express_Mini +Connector_PCBEdge:BUS_PCI_Express_Mini_Dual +Connector_PCBEdge:BUS_PCI_Express_Mini_Full +Connector_PCBEdge:BUS_PCI_Express_Mini_Half +Connector_PCBEdge:JAE_MM60-EZH039-Bx_BUS_PCI_Express_Holder +Connector_PCBEdge:JAE_MM60-EZH059-Bx_BUS_PCI_Express_Holder +Connector_PCBEdge:molex_EDGELOCK_2-CKT +Connector_PCBEdge:molex_EDGELOCK_4-CKT +Connector_PCBEdge:molex_EDGELOCK_6-CKT +Connector_PCBEdge:molex_EDGELOCK_8-CKT +Connector_PCBEdge:Samtec_MECF-05-01-L-DV-WT_2x05_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-05-01-L-DV_2x05_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-05-01-NP-L-DV-WT_2x05_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-05-01-NP-L-DV_2x05_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-05-02-L-DV-WT_2x05_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-05-02-L-DV_2x05_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-05-02-NP-L-DV-WT_2x05_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-05-02-NP-L-DV_2x05_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-05-0_-L-DV_2x05_P1.27mm_Polarized_Edge +Connector_PCBEdge:Samtec_MECF-05-0_-NP-L-DV_2x05_P1.27mm_Edge +Connector_PCBEdge:Samtec_MECF-08-01-L-DV-WT_2x08_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-08-01-L-DV_2x08_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-08-01-NP-L-DV-WT_2x08_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-08-01-NP-L-DV_2x08_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-08-02-L-DV-WT_2x08_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-08-02-L-DV_2x08_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-08-02-NP-L-DV-WT_2x08_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-08-02-NP-L-DV_2x08_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-08-0_-L-DV_2x08_P1.27mm_Polarized_Edge +Connector_PCBEdge:Samtec_MECF-08-0_-NP-L-DV_2x08_P1.27mm_Edge +Connector_PCBEdge:Samtec_MECF-20-01-L-DV-WT_2x20_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-20-01-L-DV_2x20_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-20-01-NP-L-DV-WT_2x20_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-20-01-NP-L-DV_2x20_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-20-02-L-DV-WT_2x20_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-20-02-L-DV_2x20_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-20-02-NP-L-DV-WT_2x20_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-20-02-NP-L-DV_2x20_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-20-0_-L-DV_2x20_P1.27mm_Polarized_Edge +Connector_PCBEdge:Samtec_MECF-20-0_-NP-L-DV_2x20_P1.27mm_Edge +Connector_PCBEdge:Samtec_MECF-30-01-L-DV-WT_2x30_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-30-01-L-DV_2x30_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-30-01-NP-L-DV-WT_2x30_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-30-01-NP-L-DV_2x30_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-30-02-L-DV-WT_2x30_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-30-02-L-DV_2x30_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-30-02-NP-L-DV-WT_2x30_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-30-02-NP-L-DV_2x30_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-30-0_-L-DV_2x30_P1.27mm_Polarized_Edge +Connector_PCBEdge:Samtec_MECF-30-0_-NP-L-DV_2x30_P1.27mm_Edge +Connector_PCBEdge:Samtec_MECF-40-01-L-DV-WT_2x40_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-40-01-L-DV_2x40_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-40-01-NP-L-DV-WT_2x40_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-40-01-NP-L-DV_2x40_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-40-02-L-DV-WT_2x40_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-40-02-L-DV_2x40_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-40-02-NP-L-DV-WT_2x40_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-40-02-NP-L-DV_2x40_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-40-0_-L-DV_2x40_P1.27mm_Polarized_Edge +Connector_PCBEdge:Samtec_MECF-40-0_-NP-L-DV_2x40_P1.27mm_Edge +Connector_PCBEdge:Samtec_MECF-50-01-L-DV-WT_2x50_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-50-01-L-DV_2x50_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-50-01-NP-L-DV-WT_2x50_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-50-01-NP-L-DV_2x50_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-50-02-L-DV-WT_2x50_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-50-02-L-DV_2x50_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-50-02-NP-L-DV-WT_2x50_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-50-02-NP-L-DV_2x50_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-50-0_-L-DV_2x50_P1.27mm_Polarized_Edge +Connector_PCBEdge:Samtec_MECF-50-0_-NP-L-DV_2x50_P1.27mm_Edge +Connector_PCBEdge:Samtec_MECF-60-01-L-DV-WT_2x60_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-60-01-L-DV_2x60_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-60-01-NP-L-DV-WT_2x60_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-60-01-NP-L-DV_2x60_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-60-02-L-DV-WT_2x60_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-60-02-L-DV_2x60_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-60-02-NP-L-DV-WT_2x60_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-60-02-NP-L-DV_2x60_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-60-0_-L-DV_2x60_P1.27mm_Polarized_Edge +Connector_PCBEdge:Samtec_MECF-60-0_-NP-L-DV_2x60_P1.27mm_Edge +Connector_PCBEdge:Samtec_MECF-70-01-L-DV-WT_2x70_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-70-01-L-DV_2x70_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-70-01-NP-L-DV-WT_2x70_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-70-01-NP-L-DV_2x70_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-70-02-L-DV-WT_2x70_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-70-02-L-DV_2x70_P1.27mm_Polarized_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-70-02-NP-L-DV-WT_2x70_P1.27mm_Socket_Horizontal +Connector_PCBEdge:Samtec_MECF-70-02-NP-L-DV_2x70_P1.27mm_Socket_Horizontal +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 +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_11-G_1x11_P7.50mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_12-G-7,62_1x12_P7.62mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_12-G_1x12_P7.50mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_2-G-7,62_1x02_P7.62mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_2-G_1x02_P7.50mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_3-G-7,62_1x03_P7.62mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_3-G_1x03_P7.50mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_4-G-7,62_1x04_P7.62mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_4-G_1x04_P7.50mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_5-G-7,62_1x05_P7.62mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_5-G_1x05_P7.50mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_6-G-7,62_1x06_P7.62mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_6-G_1x06_P7.50mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_7-G-7,62_1x07_P7.62mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_7-G_1x07_P7.50mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_8-G-7,62_1x08_P7.62mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_8-G_1x08_P7.50mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_9-G-7,62_1x09_P7.62mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_9-G_1x09_P7.50mm_Horizontal +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_10-G-7,62_1x10_P7.62mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_10-G_1x10_P7.50mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_11-G-7,62_1x11_P7.62mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_11-G_1x11_P7.50mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_12-G-7,62_1x12_P7.62mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_12-G_1x12_P7.50mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_2-G-7,62_1x02_P7.62mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_2-G_1x02_P7.50mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_3-G-7,62_1x03_P7.62mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_3-G_1x03_P7.50mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_4-G-7,62_1x04_P7.62mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_4-G_1x04_P7.50mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_5-G-7,62_1x05_P7.62mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_5-G_1x05_P7.50mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_6-G-7,62_1x06_P7.62mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_6-G_1x06_P7.50mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_7-G-7,62_1x07_P7.62mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_7-G_1x07_P7.50mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_8-G-7,62_1x08_P7.62mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_8-G_1x08_P7.50mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_9-G-7,62_1x09_P7.62mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBVA_2,5_9-G_1x09_P7.50mm_Vertical +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_10-GF-7,62_1x10_P7.62mm_Vertical_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_10-GF-7,62_1x10_P7.62mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_11-GF-7,62_1x11_P7.62mm_Vertical_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_11-GF-7,62_1x11_P7.62mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_12-GF-7,62_1x12_P7.62mm_Vertical_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_12-GF-7,62_1x12_P7.62mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_2-GF-7,62_1x02_P7.62mm_Vertical_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_2-GF-7,62_1x02_P7.62mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_3-GF-7,62_1x03_P7.62mm_Vertical_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_3-GF-7,62_1x03_P7.62mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_4-GF-7,62_1x04_P7.62mm_Vertical_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_4-GF-7,62_1x04_P7.62mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_5-GF-7,62_1x05_P7.62mm_Vertical_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_5-GF-7,62_1x05_P7.62mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_6-GF-7,62_1x06_P7.62mm_Vertical_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_6-GF-7,62_1x06_P7.62mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_7-GF-7,62_1x07_P7.62mm_Vertical_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_7-GF-7,62_1x07_P7.62mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_8-GF-7,62_1x08_P7.62mm_Vertical_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_8-GF-7,62_1x08_P7.62mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_9-GF-7,62_1x09_P7.62mm_Vertical_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTBV_2,5_9-GF-7,62_1x09_P7.62mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_10-GF-7,62_1x10_P7.62mm_Horizontal_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_10-GF-7,62_1x10_P7.62mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_11-GF-7,62_1x11_P7.62mm_Horizontal_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_11-GF-7,62_1x11_P7.62mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_12-GF-7,62_1x12_P7.62mm_Horizontal_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_12-GF-7,62_1x12_P7.62mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_2-GF-7,62_1x02_P7.62mm_Horizontal_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_2-GF-7,62_1x02_P7.62mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_3-GF-7,62_1x03_P7.62mm_Horizontal_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_3-GF-7,62_1x03_P7.62mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_4-GF-7,62_1x04_P7.62mm_Horizontal_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_4-GF-7,62_1x04_P7.62mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_5-GF-7,62_1x05_P7.62mm_Horizontal_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_5-GF-7,62_1x05_P7.62mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_6-GF-7,62_1x06_P7.62mm_Horizontal_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_6-GF-7,62_1x06_P7.62mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_7-GF-7,62_1x07_P7.62mm_Horizontal_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_7-GF-7,62_1x07_P7.62mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_8-GF-7,62_1x08_P7.62mm_Horizontal_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_8-GF-7,62_1x08_P7.62mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_9-GF-7,62_1x09_P7.62mm_Horizontal_ThreadedFlange +Connector_Phoenix_GMSTB:PhoenixContact_GMSTB_2,5_9-GF-7,62_1x09_P7.62mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_10-G-3.5_1x10_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_10-G-3.81_1x10_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_10-GF-3.5_1x10_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_10-GF-3.5_1x10_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_10-GF-3.81_1x10_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_10-GF-3.81_1x10_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_11-G-3.5_1x11_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_11-G-3.81_1x11_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_11-GF-3.5_1x11_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_11-GF-3.5_1x11_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_11-GF-3.81_1x11_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_11-GF-3.81_1x11_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_12-G-3.5_1x12_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_12-G-3.81_1x12_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_12-GF-3.5_1x12_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_12-GF-3.5_1x12_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_12-GF-3.81_1x12_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_12-GF-3.81_1x12_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_13-G-3.5_1x13_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_13-G-3.81_1x13_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_13-GF-3.5_1x13_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_13-GF-3.5_1x13_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_13-GF-3.81_1x13_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_13-GF-3.81_1x13_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_14-G-3.5_1x14_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_14-G-3.81_1x14_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_14-GF-3.5_1x14_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_14-GF-3.5_1x14_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_14-GF-3.81_1x14_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_14-GF-3.81_1x14_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_15-G-3.5_1x15_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_15-G-3.81_1x15_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_15-GF-3.5_1x15_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_15-GF-3.5_1x15_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_15-GF-3.81_1x15_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_15-GF-3.81_1x15_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_16-G-3.5_1x16_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_16-G-3.81_1x16_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_16-GF-3.5_1x16_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_16-GF-3.5_1x16_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_16-GF-3.81_1x16_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_16-GF-3.81_1x16_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_2-G-3.5_1x02_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_2-G-3.81_1x02_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_2-GF-3.5_1x02_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_2-GF-3.5_1x02_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_2-GF-3.81_1x02_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_2-GF-3.81_1x02_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_3-G-3.5_1x03_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_3-G-3.81_1x03_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_3-GF-3.5_1x03_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_3-GF-3.5_1x03_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_3-GF-3.81_1x03_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_3-GF-3.81_1x03_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_4-G-3.5_1x04_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_4-G-3.81_1x04_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_4-GF-3.5_1x04_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_4-GF-3.5_1x04_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_4-GF-3.81_1x04_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_4-GF-3.81_1x04_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_5-G-3.5_1x05_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_5-G-3.81_1x05_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_5-GF-3.5_1x05_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_5-GF-3.5_1x05_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_5-GF-3.81_1x05_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_5-GF-3.81_1x05_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_6-G-3.5_1x06_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_6-G-3.81_1x06_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_6-GF-3.5_1x06_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_6-GF-3.5_1x06_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_6-GF-3.81_1x06_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_6-GF-3.81_1x06_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_7-G-3.5_1x07_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_7-G-3.81_1x07_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_7-GF-3.5_1x07_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_7-GF-3.5_1x07_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_7-GF-3.81_1x07_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_7-GF-3.81_1x07_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_8-G-3.5_1x08_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_8-G-3.81_1x08_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_8-GF-3.5_1x08_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_8-GF-3.5_1x08_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_8-GF-3.81_1x08_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_8-GF-3.81_1x08_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_9-G-3.5_1x09_P3.50mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_9-G-3.81_1x09_P3.81mm_Vertical +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_9-GF-3.5_1x09_P3.50mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_9-GF-3.5_1x09_P3.50mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_9-GF-3.81_1x09_P3.81mm_Vertical_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MCV_1,5_9-GF-3.81_1x09_P3.81mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_10-G-3.5_1x10_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_10-G-3.81_1x10_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_10-GF-3.5_1x10_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_10-GF-3.5_1x10_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_10-GF-3.81_1x10_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_10-GF-3.81_1x10_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_11-G-3.5_1x11_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_11-G-3.81_1x11_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_11-GF-3.5_1x11_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_11-GF-3.5_1x11_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_11-GF-3.81_1x11_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_11-GF-3.81_1x11_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_12-G-3.5_1x12_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_12-G-3.81_1x12_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_12-GF-3.5_1x12_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_12-GF-3.5_1x12_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_12-GF-3.81_1x12_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_12-GF-3.81_1x12_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_13-G-3.5_1x13_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_13-G-3.81_1x13_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_13-GF-3.5_1x13_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_13-GF-3.5_1x13_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_13-GF-3.81_1x13_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_13-GF-3.81_1x13_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_14-G-3.5_1x14_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_14-G-3.81_1x14_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_14-GF-3.5_1x14_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_14-GF-3.5_1x14_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_14-GF-3.81_1x14_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_14-GF-3.81_1x14_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_15-G-3.5_1x15_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_15-G-3.81_1x15_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_15-GF-3.5_1x15_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_15-GF-3.5_1x15_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_15-GF-3.81_1x15_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_15-GF-3.81_1x15_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_16-G-3.5_1x16_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_16-G-3.81_1x16_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_16-GF-3.5_1x16_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_16-GF-3.5_1x16_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_16-GF-3.81_1x16_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_16-GF-3.81_1x16_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_2-G-3.5_1x02_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_2-G-3.81_1x02_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_2-GF-3.5_1x02_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_2-GF-3.5_1x02_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_2-GF-3.81_1x02_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_2-GF-3.81_1x02_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_3-G-3.5_1x03_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_3-G-3.81_1x03_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_3-GF-3.5_1x03_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_3-GF-3.5_1x03_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_3-GF-3.81_1x03_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_3-GF-3.81_1x03_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_4-G-3.5_1x04_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_4-G-3.81_1x04_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_4-GF-3.5_1x04_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_4-GF-3.5_1x04_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_4-GF-3.81_1x04_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_4-GF-3.81_1x04_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_5-G-3.5_1x05_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_5-G-3.81_1x05_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_5-GF-3.5_1x05_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_5-GF-3.5_1x05_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_5-GF-3.81_1x05_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_5-GF-3.81_1x05_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_6-G-3.5_1x06_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_6-G-3.81_1x06_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_6-GF-3.5_1x06_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_6-GF-3.5_1x06_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_6-GF-3.81_1x06_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_6-GF-3.81_1x06_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_7-G-3.5_1x07_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_7-G-3.81_1x07_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_7-GF-3.5_1x07_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_7-GF-3.5_1x07_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_7-GF-3.81_1x07_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_7-GF-3.81_1x07_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_8-G-3.5_1x08_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_8-G-3.81_1x08_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_8-GF-3.5_1x08_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_8-GF-3.5_1x08_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_8-GF-3.81_1x08_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_8-GF-3.81_1x08_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_9-G-3.5_1x09_P3.50mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_9-G-3.81_1x09_P3.81mm_Horizontal +Connector_Phoenix_MC:PhoenixContact_MC_1,5_9-GF-3.5_1x09_P3.50mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_9-GF-3.5_1x09_P3.50mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC:PhoenixContact_MC_1,5_9-GF-3.81_1x09_P3.81mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC:PhoenixContact_MC_1,5_9-GF-3.81_1x09_P3.81mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_10-G-5.08_1x10_P5.08mm_Vertical +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_10-GF-5.08_1x10_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_10-GF-5.08_1x10_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_11-G-5.08_1x11_P5.08mm_Vertical +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_11-GF-5.08_1x11_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_11-GF-5.08_1x11_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_12-G-5.08_1x12_P5.08mm_Vertical +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_12-GF-5.08_1x12_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_12-GF-5.08_1x12_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_2-G-5.08_1x02_P5.08mm_Vertical +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_2-GF-5.08_1x02_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_2-GF-5.08_1x02_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_3-G-5.08_1x03_P5.08mm_Vertical +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_3-GF-5.08_1x03_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_3-GF-5.08_1x03_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_4-G-5.08_1x04_P5.08mm_Vertical +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_4-GF-5.08_1x04_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_4-GF-5.08_1x04_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_5-G-5.08_1x05_P5.08mm_Vertical +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_5-GF-5.08_1x05_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_5-GF-5.08_1x05_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_6-G-5.08_1x06_P5.08mm_Vertical +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_6-GF-5.08_1x06_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_6-GF-5.08_1x06_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_7-G-5.08_1x07_P5.08mm_Vertical +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_7-GF-5.08_1x07_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_7-GF-5.08_1x07_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_8-G-5.08_1x08_P5.08mm_Vertical +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_8-GF-5.08_1x08_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_8-GF-5.08_1x08_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_9-G-5.08_1x09_P5.08mm_Vertical +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_9-GF-5.08_1x09_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MCV_1,5_9-GF-5.08_1x09_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_10-G-5.08_1x10_P5.08mm_Horizontal +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_10-GF-5.08_1x10_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_10-GF-5.08_1x10_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_11-G-5.08_1x11_P5.08mm_Horizontal +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_11-GF-5.08_1x11_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_11-GF-5.08_1x11_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_12-G-5.08_1x12_P5.08mm_Horizontal +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_12-GF-5.08_1x12_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_12-GF-5.08_1x12_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_2-G-5.08_1x02_P5.08mm_Horizontal +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_2-GF-5.08_1x02_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_2-GF-5.08_1x02_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_3-G-5.08_1x03_P5.08mm_Horizontal +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_3-GF-5.08_1x03_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_3-GF-5.08_1x03_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_4-G-5.08_1x04_P5.08mm_Horizontal +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_4-GF-5.08_1x04_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_4-GF-5.08_1x04_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_5-G-5.08_1x05_P5.08mm_Horizontal +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_5-GF-5.08_1x05_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_5-GF-5.08_1x05_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_6-G-5.08_1x06_P5.08mm_Horizontal +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_6-GF-5.08_1x06_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_6-GF-5.08_1x06_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_7-G-5.08_1x07_P5.08mm_Horizontal +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_7-GF-5.08_1x07_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_7-GF-5.08_1x07_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_8-G-5.08_1x08_P5.08mm_Horizontal +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_8-GF-5.08_1x08_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_8-GF-5.08_1x08_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_9-G-5.08_1x09_P5.08mm_Horizontal +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_9-GF-5.08_1x09_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MC_HighVoltage:PhoenixContact_MC_1,5_9-GF-5.08_1x09_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_10-G-5,08_1x10_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_10-G_1x10_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_11-G-5,08_1x11_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_11-G_1x11_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_12-G-5,08_1x12_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_12-G_1x12_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_13-G-5,08_1x13_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_13-G_1x13_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_14-G-5,08_1x14_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_14-G_1x14_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_15-G-5,08_1x15_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_15-G_1x15_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_16-G-5,08_1x16_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_16-G_1x16_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_2-G-5,08_1x02_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_2-G_1x02_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_3-G-5,08_1x03_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_3-G_1x03_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_4-G-5,08_1x04_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_4-G_1x04_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_5-G-5,08_1x05_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_5-G_1x05_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_6-G-5,08_1x06_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_6-G_1x06_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_7-G-5,08_1x07_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_7-G_1x07_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_8-G-5,08_1x08_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_8-G_1x08_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_9-G-5,08_1x09_P5.08mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBA_2,5_9-G_1x09_P5.00mm_Horizontal +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_10-G-5,08_1x10_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_10-G_1x10_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_11-G-5,08_1x11_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_11-G_1x11_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_12-G-5,08_1x12_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_12-G_1x12_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_13-G-5,08_1x13_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_13-G_1x13_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_14-G-5,08_1x14_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_14-G_1x14_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_15-G-5,08_1x15_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_15-G_1x15_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_16-G-5,08_1x16_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_16-G_1x16_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_2-G-5,08_1x02_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_2-G_1x02_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_3-G-5,08_1x03_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_3-G_1x03_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_4-G-5,08_1x04_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_4-G_1x04_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_5-G-5,08_1x05_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_5-G_1x05_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_6-G-5,08_1x06_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_6-G_1x06_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_7-G-5,08_1x07_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_7-G_1x07_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_8-G-5,08_1x08_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_8-G_1x08_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_9-G-5,08_1x09_P5.08mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBVA_2,5_9-G_1x09_P5.00mm_Vertical +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_10-GF-5,08_1x10_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_10-GF-5,08_1x10_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_10-GF_1x10_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_10-GF_1x10_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_11-GF-5,08_1x11_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_11-GF-5,08_1x11_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_11-GF_1x11_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_11-GF_1x11_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_12-GF-5,08_1x12_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_12-GF-5,08_1x12_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_12-GF_1x12_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_12-GF_1x12_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_13-GF-5,08_1x13_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_13-GF-5,08_1x13_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_13-GF_1x13_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_13-GF_1x13_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_14-GF-5,08_1x14_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_14-GF-5,08_1x14_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_14-GF_1x14_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_14-GF_1x14_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_15-GF-5,08_1x15_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_15-GF-5,08_1x15_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_15-GF_1x15_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_15-GF_1x15_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_16-GF-5,08_1x16_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_16-GF-5,08_1x16_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_16-GF_1x16_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_16-GF_1x16_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_2-GF-5,08_1x02_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_2-GF-5,08_1x02_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_2-GF_1x02_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_2-GF_1x02_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_3-GF-5,08_1x03_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_3-GF-5,08_1x03_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_3-GF_1x03_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_3-GF_1x03_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_4-GF-5,08_1x04_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_4-GF-5,08_1x04_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_4-GF_1x04_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_4-GF_1x04_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_5-GF-5,08_1x05_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_5-GF-5,08_1x05_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_5-GF_1x05_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_5-GF_1x05_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_6-GF-5,08_1x06_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_6-GF-5,08_1x06_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_6-GF_1x06_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_6-GF_1x06_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_7-GF-5,08_1x07_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_7-GF-5,08_1x07_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_7-GF_1x07_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_7-GF_1x07_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_8-GF-5,08_1x08_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_8-GF-5,08_1x08_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_8-GF_1x08_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_8-GF_1x08_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_9-GF-5,08_1x09_P5.08mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_9-GF-5,08_1x09_P5.08mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_9-GF_1x09_P5.00mm_Vertical_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTBV_2,5_9-GF_1x09_P5.00mm_Vertical_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_10-GF-5,08_1x10_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_10-GF-5,08_1x10_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_10-GF_1x10_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_10-GF_1x10_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_11-GF-5,08_1x11_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_11-GF-5,08_1x11_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_11-GF_1x11_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_11-GF_1x11_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_12-GF-5,08_1x12_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_12-GF-5,08_1x12_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_12-GF_1x12_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_12-GF_1x12_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_13-GF-5,08_1x13_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_13-GF-5,08_1x13_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_13-GF_1x13_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_13-GF_1x13_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_14-GF-5,08_1x14_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_14-GF-5,08_1x14_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_14-GF_1x14_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_14-GF_1x14_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_15-GF-5,08_1x15_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_15-GF-5,08_1x15_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_15-GF_1x15_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_15-GF_1x15_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_16-GF-5,08_1x16_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_16-GF-5,08_1x16_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_16-GF_1x16_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_16-GF_1x16_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_2-GF-5,08_1x02_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_2-GF-5,08_1x02_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_2-GF_1x02_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_2-GF_1x02_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_3-GF-5,08_1x03_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_3-GF-5,08_1x03_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_3-GF_1x03_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_3-GF_1x03_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_4-GF-5,08_1x04_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_4-GF-5,08_1x04_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_4-GF_1x04_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_4-GF_1x04_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_5-GF-5,08_1x05_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_5-GF-5,08_1x05_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_5-GF_1x05_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_5-GF_1x05_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_6-GF-5,08_1x06_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_6-GF-5,08_1x06_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_6-GF_1x06_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_6-GF_1x06_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_7-GF-5,08_1x07_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_7-GF-5,08_1x07_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_7-GF_1x07_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_7-GF_1x07_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_8-GF-5,08_1x08_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_8-GF-5,08_1x08_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_8-GF_1x08_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_8-GF_1x08_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_9-GF-5,08_1x09_P5.08mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_9-GF-5,08_1x09_P5.08mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_9-GF_1x09_P5.00mm_Horizontal_ThreadedFlange +Connector_Phoenix_MSTB:PhoenixContact_MSTB_2,5_9-GF_1x09_P5.00mm_Horizontal_ThreadedFlange_MountHole +Connector_Phoenix_SPT:PhoenixContact_SPT_1.5_10-H-3.5_1x10_P3.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_1.5_11-H-3.5_1x11_P3.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_1.5_12-H-3.5_1x12_P3.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_1.5_2-H-3.5_1x02_P3.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_1.5_3-H-3.5_1x03_P3.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_1.5_4-H-3.5_1x04_P3.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_1.5_5-H-3.5_1x05_P3.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_1.5_6-H-3.5_1x06_P3.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_1.5_7-H-3.5_1x07_P3.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_1.5_8-H-3.5_1x08_P3.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_1.5_9-H-3.5_1x09_P3.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_1-H-5.0_1x01_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_10-H-5.0-EX_1x10_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_10-H-5.0_1x10_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_10-V-5.0-EX_1x10_P5.0mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_11-H-5.0-EX_1x11_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_11-H-5.0_1x11_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_11-V-5.0-EX_1x11_P5.0mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_12-H-5.0-EX_1x12_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_12-H-5.0_1x12_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_12-V-5.0-EX_1x12_P5.0mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_2-H-5.0-EX_1x02_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_2-H-5.0_1x02_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_2-V-5.0-EX_1x02_P5.0mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_3-H-5.0-EX_1x03_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_3-H-5.0_1x03_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_3-V-5.0-EX_1x03_P5.0mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_4-H-5.0-EX_1x04_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_4-H-5.0_1x04_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_4-V-5.0-EX_1x04_P5.0mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_5-H-5.0-EX_1x05_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_5-H-5.0_1x05_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_5-V-5.0-EX_1x05_P5.0mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_6-H-5.0-EX_1x06_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_6-H-5.0_1x06_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_6-V-5.0-EX_1x06_P5.0mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_7-H-5.0-EX_1x07_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_7-H-5.0_1x07_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_7-V-5.0-EX_1x07_P5.0mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_8-H-5.0-EX_1x08_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_8-H-5.0_1x08_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_8-V-5.0-EX_1x08_P5.0mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_9-H-5.0-EX_1x09_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_9-H-5.0_1x09_P5.0mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_2.5_9-V-5.0-EX_1x09_P5.0mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_5_1-H-7.5_1x01_P7.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_5_1-V-7.5_1x01_P7.5mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_5_10-H-7.5-ZB_1x10_P7.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_5_10-V-7.5-ZB_1x10_P7.5mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_5_11-H-7.5-ZB_1x11_P7.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_5_11-V-7.5-ZB_1x11_P7.5mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_5_12-H-7.5-ZB_1x12_P7.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_5_12-V-7.5-ZB_1x12_P7.5mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_5_2-H-7.5-ZB_1x02_P7.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_5_2-V-7.5_1x02_P7.5mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_5_3-H-7.5-ZB_1x03_P7.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_5_3-H-7.5_1x03_P7.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_5_3-V-7.5-ZB_1x03_P7.5mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_5_4-H-7.5-ZB_1x04_P7.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_5_5-H-7.5-ZB_1x05_P7.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_5_5-V-7.5-ZB_1x05_P7.5mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_5_6-H-7.5-ZB_1x06_P7.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_5_6-V-7.5-ZB_1x06_P7.5mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_5_7-H-7.5-ZB_1x07_P7.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_5_7-V-7.5-ZB_1x07_P7.5mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_5_8-H-7.5-ZB_1x08_P7.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_5_8-V-7.5-ZB_1x08_P7.5mm_Vertical +Connector_Phoenix_SPT:PhoenixContact_SPT_5_9-H-7.5-ZB_1x09_P7.5mm_Horizontal +Connector_Phoenix_SPT:PhoenixContact_SPT_5_9-V-7.5-ZB_1x09_P7.5mm_Vertical +Connector_Pin:Pin_D0.7mm_L6.5mm_W1.8mm_FlatFork +Connector_Pin:Pin_D0.9mm_L10.0mm_W2.4mm_FlatFork +Connector_Pin:Pin_D1.0mm_L10.0mm +Connector_Pin:Pin_D1.0mm_L10.0mm_LooseFit +Connector_Pin:Pin_D1.1mm_L10.2mm_W3.5mm_Flat +Connector_Pin:Pin_D1.1mm_L8.5mm_W2.5mm_FlatFork +Connector_Pin:Pin_D1.2mm_L10.2mm_W2.9mm_FlatFork +Connector_Pin:Pin_D1.2mm_L11.3mm_W3.0mm_Flat +Connector_Pin:Pin_D1.3mm_L10.0mm_W3.5mm_Flat +Connector_Pin:Pin_D1.3mm_L11.0mm +Connector_Pin:Pin_D1.3mm_L11.0mm_LooseFit +Connector_Pin:Pin_D1.3mm_L11.3mm_W2.8mm_Flat +Connector_Pin:Pin_D1.4mm_L8.5mm_W2.8mm_FlatFork +Connector_PinHeader_1.00mm:PinHeader_1x01_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x01_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x02_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x02_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x02_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x02_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x03_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x03_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x03_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x03_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x04_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x04_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x04_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x04_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x05_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x05_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x05_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x05_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x06_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x06_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x06_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x06_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x07_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x07_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x07_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x07_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x08_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x08_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x08_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x08_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x09_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x09_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x09_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x09_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x10_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x10_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x10_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x10_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x11_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x11_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x11_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x11_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x12_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x12_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x12_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x12_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x13_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x13_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x13_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x13_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x14_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x14_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x14_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x14_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x15_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x15_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x15_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x15_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x16_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x16_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x16_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x16_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x17_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x17_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x17_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x17_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x18_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x18_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x18_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x18_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x19_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x19_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x19_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x19_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x20_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x20_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x20_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x20_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x21_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x21_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x21_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x21_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x22_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x22_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x22_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x22_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x23_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x23_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x23_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x23_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x24_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x24_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x24_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x24_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x25_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x25_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x25_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x25_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x26_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x26_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x26_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x26_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x27_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x27_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x27_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x27_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x28_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x28_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x28_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x28_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x29_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x29_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x29_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x29_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x30_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x30_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x30_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x30_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x31_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x31_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x31_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x31_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x32_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x32_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x32_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x32_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x33_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x33_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x33_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x33_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x34_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x34_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x34_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x34_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x35_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x35_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x35_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x35_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x36_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x36_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x36_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x36_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x37_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x37_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x37_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x37_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x38_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x38_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x38_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x38_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x39_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x39_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x39_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x39_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_1x40_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_1x40_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_1x40_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.00mm:PinHeader_1x40_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.00mm:PinHeader_2x01_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x01_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x01_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x02_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x02_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x02_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x03_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x03_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x03_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x04_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x04_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x04_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x05_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x05_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x05_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x06_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x06_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x06_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x07_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x07_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x07_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x08_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x08_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x08_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x09_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x09_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x09_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x10_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x10_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x10_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x11_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x11_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x11_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x12_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x12_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x12_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x13_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x13_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x13_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x14_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x14_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x14_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x15_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x15_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x15_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x16_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x16_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x16_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x17_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x17_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x17_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x18_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x18_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x18_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x19_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x19_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x19_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x20_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x20_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x20_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x21_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x21_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x21_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x22_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x22_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x22_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x23_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x23_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x23_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x24_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x24_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x24_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x25_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x25_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x25_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x26_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x26_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x26_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x27_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x27_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x27_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x28_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x28_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x28_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x29_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x29_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x29_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x30_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x30_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x30_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x31_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x31_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x31_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x32_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x32_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x32_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x33_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x33_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x33_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x34_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x34_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x34_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x35_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x35_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x35_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x36_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x36_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x36_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x37_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x37_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x37_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x38_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x38_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x38_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x39_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x39_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x39_P1.00mm_Vertical_SMD +Connector_PinHeader_1.00mm:PinHeader_2x40_P1.00mm_Horizontal +Connector_PinHeader_1.00mm:PinHeader_2x40_P1.00mm_Vertical +Connector_PinHeader_1.00mm:PinHeader_2x40_P1.00mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_1x01_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x01_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x02_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x02_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x02_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x02_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x03_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x03_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x03_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x03_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x04_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x04_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x04_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x04_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x05_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x05_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x05_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x05_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x06_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x06_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x06_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x06_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x07_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x07_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x07_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x07_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x08_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x08_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x08_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x08_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x09_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x09_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x09_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x09_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x10_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x10_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x10_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x10_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x11_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x11_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x11_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x11_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x12_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x12_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x12_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x12_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x13_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x13_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x13_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x13_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x14_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x14_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x14_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x14_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x15_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x15_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x15_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x15_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x16_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x16_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x16_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x16_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x17_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x17_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x17_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x17_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x18_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x18_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x18_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x18_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x19_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x19_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x19_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x19_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x20_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x20_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x20_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x20_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x21_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x21_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x21_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x21_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x22_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x22_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x22_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x22_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x23_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x23_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x23_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x23_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x24_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x24_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x24_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x24_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x25_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x25_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x25_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x25_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x26_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x26_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x26_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x26_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x27_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x27_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x27_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x27_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x28_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x28_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x28_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x28_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x29_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x29_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x29_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x29_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x30_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x30_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x30_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x30_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x31_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x31_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x31_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x31_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x32_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x32_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x32_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x32_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x33_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x33_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x33_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x33_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x34_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x34_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x34_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x34_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x35_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x35_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x35_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x35_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x36_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x36_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x36_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x36_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x37_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x37_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x37_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x37_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x38_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x38_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x38_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x38_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x39_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x39_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x39_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x39_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_1x40_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_1x40_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_1x40_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinHeader_1.27mm:PinHeader_1x40_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinHeader_1.27mm:PinHeader_2x01_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x01_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x01_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x02_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x02_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x02_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x03_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x03_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x03_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x04_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x04_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x04_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x05_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x05_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x05_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x06_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x06_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x06_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x07_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x07_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x07_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x08_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x08_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x08_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x09_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x09_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x09_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x10_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x10_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x10_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x11_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x11_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x11_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x12_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x12_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x12_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x13_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x13_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x13_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x14_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x14_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x14_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x15_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x15_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x15_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x16_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x16_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x16_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x17_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x17_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x17_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x18_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x18_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x18_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x19_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x19_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x19_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x20_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x20_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x20_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x21_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x21_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x21_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x22_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x22_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x22_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x23_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x23_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x23_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x24_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x24_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x24_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x25_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x25_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x25_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x26_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x26_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x26_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x27_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x27_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x27_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x28_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x28_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x28_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x29_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x29_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x29_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x30_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x30_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x30_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x31_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x31_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x31_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x32_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x32_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x32_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x33_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x33_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x33_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x34_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x34_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x34_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x35_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x35_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x35_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x36_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x36_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x36_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x37_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x37_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x37_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x38_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x38_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x38_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x39_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x39_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x39_P1.27mm_Vertical_SMD +Connector_PinHeader_1.27mm:PinHeader_2x40_P1.27mm_Horizontal +Connector_PinHeader_1.27mm:PinHeader_2x40_P1.27mm_Vertical +Connector_PinHeader_1.27mm:PinHeader_2x40_P1.27mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_1x01_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x01_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x02_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x02_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x02_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x02_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x03_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x03_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x03_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x03_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x04_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x04_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x04_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x04_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x05_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x05_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x05_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x05_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x06_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x06_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x06_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x06_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x07_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x07_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x07_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x07_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x08_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x08_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x08_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x08_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x09_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x09_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x09_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x09_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x10_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x10_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x10_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x10_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x11_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x11_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x11_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x11_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x12_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x12_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x12_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x12_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x13_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x13_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x13_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x13_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x14_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x14_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x14_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x14_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x15_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x15_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x15_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x15_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x16_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x16_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x16_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x16_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x17_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x17_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x17_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x17_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x18_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x18_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x18_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x18_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x19_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x19_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x19_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x19_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x20_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x20_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x20_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x20_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x21_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x21_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x21_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x21_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x22_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x22_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x22_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x22_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x23_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x23_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x23_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x23_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x24_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x24_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x24_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x24_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x25_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x25_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x25_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x25_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x26_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x26_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x26_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x26_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x27_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x27_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x27_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x27_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x28_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x28_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x28_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x28_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x29_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x29_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x29_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x29_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x30_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x30_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x30_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x30_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x31_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x31_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x31_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x31_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x32_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x32_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x32_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x32_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x33_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x33_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x33_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x33_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x34_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x34_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x34_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x34_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x35_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x35_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x35_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x35_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x36_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x36_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x36_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x36_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x37_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x37_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x37_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x37_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x38_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x38_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x38_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x38_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x39_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x39_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x39_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x39_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_1x40_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_1x40_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_1x40_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.00mm:PinHeader_1x40_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.00mm:PinHeader_2x01_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x01_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x01_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x02_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x02_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x02_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x03_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x03_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x03_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x04_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x04_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x04_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x05_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x05_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x05_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x06_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x06_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x06_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x07_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x07_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x07_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x08_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x08_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x08_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x09_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x09_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x09_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x10_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x10_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x10_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x11_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x11_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x11_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x12_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x12_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x12_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x13_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x13_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x13_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x14_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x14_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x14_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x15_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x15_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x15_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x16_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x16_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x16_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x17_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x17_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x17_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x18_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x18_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x18_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x19_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x19_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x19_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x20_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x20_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x20_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x21_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x21_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x21_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x22_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x22_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x22_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x23_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x23_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x23_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x24_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x24_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x24_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x25_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x25_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x25_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x26_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x26_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x26_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x27_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x27_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x27_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x28_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x28_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x28_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x29_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x29_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x29_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x30_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x30_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x30_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x31_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x31_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x31_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x32_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x32_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x32_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x33_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x33_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x33_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x34_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x34_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x34_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x35_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x35_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x35_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x36_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x36_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x36_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x37_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x37_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x37_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x38_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x38_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x38_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x39_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x39_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x39_P2.00mm_Vertical_SMD +Connector_PinHeader_2.00mm:PinHeader_2x40_P2.00mm_Horizontal +Connector_PinHeader_2.00mm:PinHeader_2x40_P2.00mm_Vertical +Connector_PinHeader_2.00mm:PinHeader_2x40_P2.00mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_1x01_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x01_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x05_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x05_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x05_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x05_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x07_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x07_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x07_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x07_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x08_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x08_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x08_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x08_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x09_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x09_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x09_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x09_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x10_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x10_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x10_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x10_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x11_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x11_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x11_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x11_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x12_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x12_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x12_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x12_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x13_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x13_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x13_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x13_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x14_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x14_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x14_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x14_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x15_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x15_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x15_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x15_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x16_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x16_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x16_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x16_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x17_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x17_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x17_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x17_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x18_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x18_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x18_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x18_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x19_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x19_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x19_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x19_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x20_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x20_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x20_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x20_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x21_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x21_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x21_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x21_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x22_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x22_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x22_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x22_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x23_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x23_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x23_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x23_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x24_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x24_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x24_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x24_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x25_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x25_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x25_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x25_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x26_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x26_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x26_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x26_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x27_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x27_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x27_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x27_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x28_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x28_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x28_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x28_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x29_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x29_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x29_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x29_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x30_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x30_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x30_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x30_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x31_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x31_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x31_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x31_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x32_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x32_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x32_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x32_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x33_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x33_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x33_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x33_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x34_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x34_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x34_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x34_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x35_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x35_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x35_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x35_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x36_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x36_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x36_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x36_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x37_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x37_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x37_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x37_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x38_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x38_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x38_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x38_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x39_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x39_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x39_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x39_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_1x40_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_1x40_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_1x40_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinHeader_2.54mm:PinHeader_1x40_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinHeader_2.54mm:PinHeader_2x01_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x01_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x01_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x02_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x02_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x02_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x03_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x03_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x03_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x04_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x04_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x04_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x05_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x05_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x05_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x06_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x06_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x06_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x07_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x07_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x07_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x08_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x08_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x08_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x09_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x09_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x09_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x10_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x10_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x10_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x11_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x11_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x11_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x12_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x12_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x12_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x13_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x13_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x13_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x14_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x14_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x14_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x15_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x15_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x15_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x16_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x16_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x16_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x17_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x17_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x17_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x18_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x18_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x18_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x19_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x19_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x19_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x20_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x20_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x20_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x21_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x21_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x21_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x22_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x22_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x22_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x23_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x23_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x23_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x24_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x24_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x24_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x25_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x25_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x25_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x26_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x26_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x26_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x27_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x27_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x27_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x28_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x28_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x28_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x29_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x29_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x29_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x30_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x30_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x30_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x31_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x31_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x31_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x32_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x32_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x32_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x33_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x33_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x33_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x34_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x34_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x34_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x35_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x35_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x35_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x36_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x36_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x36_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x37_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x37_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x37_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x38_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x38_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x38_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x39_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x39_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x39_P2.54mm_Vertical_SMD +Connector_PinHeader_2.54mm:PinHeader_2x40_P2.54mm_Horizontal +Connector_PinHeader_2.54mm:PinHeader_2x40_P2.54mm_Vertical +Connector_PinHeader_2.54mm:PinHeader_2x40_P2.54mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_1x02_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x02_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x02_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x03_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x03_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x03_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x04_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x04_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x04_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x05_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x05_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x05_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x06_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x06_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x06_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x07_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x07_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x07_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x08_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x08_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x08_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x09_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x09_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x09_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x10_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x10_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x10_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x11_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x11_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x11_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x12_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x12_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x12_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x13_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x13_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x13_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x14_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x14_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x14_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x15_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x15_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x15_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x16_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x16_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x16_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x17_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x17_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x17_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x18_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x18_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x18_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x19_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x19_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x19_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x20_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x20_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x20_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x21_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x21_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x21_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x22_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x22_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x22_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x23_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x23_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x23_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x24_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x24_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x24_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x25_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x25_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x25_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x26_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x26_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x26_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x27_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x27_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x27_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x28_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x28_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x28_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x29_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x29_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x29_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x30_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x30_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x30_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x31_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x31_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x31_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x32_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x32_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x32_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x33_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x33_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x33_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x34_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x34_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x34_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x35_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x35_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x35_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x36_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x36_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x36_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x37_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x37_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x37_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x38_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x38_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x38_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x39_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x39_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x39_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_1x40_P1.00mm_Vertical +Connector_PinSocket_1.00mm:PinSocket_1x40_P1.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.00mm:PinSocket_1x40_P1.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.00mm:PinSocket_2x02_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x03_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x04_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x05_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x06_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x07_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x08_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x09_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x10_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x11_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x12_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x13_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x14_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x15_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x16_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x17_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x18_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x19_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x20_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x21_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x22_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x23_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x24_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x25_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x26_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x27_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x28_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x29_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x30_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x31_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x32_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x33_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x34_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x35_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x36_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x37_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x38_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x39_P1.00mm_Vertical_SMD +Connector_PinSocket_1.00mm:PinSocket_2x40_P1.00mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_1x01_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x02_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x02_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x02_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x03_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x03_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x03_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x04_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x04_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x04_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x05_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x05_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x05_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x06_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x06_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x06_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x07_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x07_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x07_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x08_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x08_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x08_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x09_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x09_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x09_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x10_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x10_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x10_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x11_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x11_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x11_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x12_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x12_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x12_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x13_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x13_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x13_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x14_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x14_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x14_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x15_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x15_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x15_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x16_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x16_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x16_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x17_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x17_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x17_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x18_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x18_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x18_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x19_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x19_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x19_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x20_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x20_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x20_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x21_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x21_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x21_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x22_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x22_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x22_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x23_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x23_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x23_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x24_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x24_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x24_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x25_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x25_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x25_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x26_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x26_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x26_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x27_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x27_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x27_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x28_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x28_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x28_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x29_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x29_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x29_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x30_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x30_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x30_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x31_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x31_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x31_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x32_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x32_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x32_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x33_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x33_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x33_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x34_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x34_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x34_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x35_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x35_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x35_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x36_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x36_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x36_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x37_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x37_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x37_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x38_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x38_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x38_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x39_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x39_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x39_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_1x40_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_1x40_P1.27mm_Vertical_SMD_Pin1Left +Connector_PinSocket_1.27mm:PinSocket_1x40_P1.27mm_Vertical_SMD_Pin1Right +Connector_PinSocket_1.27mm:PinSocket_2x01_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x01_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x02_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x02_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x03_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x03_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x03_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x04_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x04_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x04_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x05_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x05_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x05_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x06_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x06_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x06_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x07_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x07_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x07_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x08_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x08_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x08_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x09_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x09_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x09_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x10_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x10_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x10_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x11_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x11_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x11_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x12_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x12_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x12_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x13_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x13_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x13_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x14_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x14_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x14_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x15_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x15_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x15_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x16_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x16_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x16_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x17_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x17_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x17_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x18_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x18_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x18_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x19_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x19_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x19_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x20_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x20_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x20_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x21_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x21_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x21_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x22_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x22_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x22_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x23_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x23_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x23_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x24_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x24_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x24_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x25_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x25_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x25_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x26_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x26_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x26_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x27_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x27_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x27_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x28_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x28_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x28_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x29_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x29_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x29_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x30_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x30_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x30_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x31_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x31_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x31_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x32_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x32_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x32_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x33_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x33_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x33_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x34_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x34_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x34_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x35_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x35_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x35_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x36_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x36_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x36_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x37_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x37_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x37_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x38_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x38_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x38_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x39_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x39_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x39_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x40_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x40_P1.27mm_Vertical +Connector_PinSocket_1.27mm:PinSocket_2x40_P1.27mm_Vertical_SMD +Connector_PinSocket_1.27mm:PinSocket_2x41_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x42_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x43_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x44_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x45_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x46_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x47_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x48_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x49_P1.27mm_Horizontal +Connector_PinSocket_1.27mm:PinSocket_2x50_P1.27mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x01_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x01_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x02_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x02_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x02_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x02_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x03_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x03_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x03_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x03_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x04_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x04_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x04_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x04_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x05_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x05_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x05_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x05_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x06_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x06_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x06_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x06_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x07_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x07_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x07_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x07_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x08_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x08_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x08_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x08_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x09_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x09_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x09_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x09_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x10_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x10_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x10_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x10_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x11_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x11_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x11_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x11_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x12_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x12_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x12_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x12_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x13_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x13_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x13_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x13_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x14_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x14_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x14_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x14_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x15_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x15_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x15_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x15_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x16_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x16_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x16_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x16_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x17_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x17_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x17_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x17_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x18_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x18_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x18_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x18_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x19_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x19_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x19_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x19_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x20_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x20_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x20_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x20_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x21_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x21_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x21_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x21_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x22_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x22_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x22_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x22_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x23_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x23_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x23_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x23_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x24_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x24_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x24_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x24_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x25_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x25_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x25_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x25_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x26_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x26_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x26_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x26_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x27_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x27_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x27_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x27_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x28_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x28_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x28_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x28_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x29_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x29_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x29_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x29_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x30_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x30_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x30_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x30_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x31_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x31_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x31_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x31_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x32_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x32_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x32_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x32_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x33_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x33_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x33_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x33_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x34_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x34_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x34_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x34_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x35_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x35_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x35_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x35_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x36_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x36_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x36_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x36_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x37_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x37_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x37_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x37_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x38_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x38_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x38_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x38_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x39_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x39_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x39_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x39_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_1x40_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_1x40_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_1x40_P2.00mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.00mm:PinSocket_1x40_P2.00mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.00mm:PinSocket_2x01_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x01_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x01_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x02_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x02_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x02_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x03_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x03_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x03_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x04_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x04_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x04_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x05_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x05_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x05_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x06_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x06_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x06_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x07_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x07_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x07_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x08_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x08_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x08_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x09_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x09_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x09_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x10_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x10_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x10_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x11_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x11_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x11_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x12_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x12_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x12_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x13_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x13_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x13_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x14_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x14_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x14_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x15_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x15_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x15_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x16_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x16_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x16_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x17_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x17_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x17_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x18_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x18_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x18_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x19_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x19_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x19_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x20_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x20_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x20_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x21_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x21_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x21_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x22_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x22_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x22_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x23_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x23_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x23_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x24_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x24_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x24_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x25_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x25_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x25_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x26_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x26_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x26_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x27_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x27_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x27_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x28_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x28_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x28_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x29_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x29_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x29_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x30_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x30_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x30_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x31_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x31_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x31_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x32_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x32_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x32_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x33_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x33_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x33_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x34_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x34_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x34_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x35_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x35_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x35_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x36_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x36_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x36_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x37_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x37_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x37_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x38_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x38_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x38_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x39_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x39_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x39_P2.00mm_Vertical_SMD +Connector_PinSocket_2.00mm:PinSocket_2x40_P2.00mm_Horizontal +Connector_PinSocket_2.00mm:PinSocket_2x40_P2.00mm_Vertical +Connector_PinSocket_2.00mm:PinSocket_2x40_P2.00mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_1x01_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x01_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x02_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x02_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x02_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x02_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x03_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x03_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x03_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x03_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x04_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x04_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x04_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x04_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x05_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x05_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x05_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x05_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x06_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x06_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x06_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x06_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x07_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x07_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x07_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x07_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x08_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x08_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x08_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x08_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x09_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x09_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x09_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x09_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x10_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x10_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x10_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x10_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x11_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x11_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x11_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x11_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x12_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x12_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x12_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x12_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x13_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x13_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x13_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x13_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x14_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x14_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x14_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x14_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x15_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x15_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x15_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x15_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x16_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x16_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x16_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x16_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x17_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x17_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x17_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x17_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x18_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x18_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x18_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x18_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x19_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x19_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x19_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x19_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x20_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x20_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x20_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x20_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x21_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x21_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x21_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x21_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x22_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x22_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x22_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x22_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x23_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x23_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x23_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x23_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x24_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x24_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x24_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x24_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x25_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x25_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x25_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x25_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x26_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x26_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x26_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x26_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x27_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x27_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x27_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x27_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x28_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x28_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x28_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x28_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x29_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x29_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x29_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x29_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x30_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x30_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x30_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x30_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x31_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x31_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x31_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x31_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x32_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x32_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x32_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x32_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x33_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x33_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x33_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x33_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x34_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x34_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x34_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x34_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x35_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x35_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x35_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x35_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x36_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x36_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x36_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x36_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x37_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x37_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x37_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x37_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x38_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x38_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x38_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x38_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x39_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x39_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x39_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x39_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_1x40_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_1x40_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_1x40_P2.54mm_Vertical_SMD_Pin1Left +Connector_PinSocket_2.54mm:PinSocket_1x40_P2.54mm_Vertical_SMD_Pin1Right +Connector_PinSocket_2.54mm:PinSocket_2x01_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x01_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x01_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x02_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x02_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x02_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x03_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x03_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x03_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x04_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x04_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x04_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x05_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x05_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x05_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x06_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x06_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x06_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x07_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x07_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x07_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x08_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x08_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x08_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x09_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x09_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x09_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x10_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x10_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x10_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x11_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x11_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x11_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x12_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x12_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x12_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x13_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x13_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x13_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x14_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x14_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x14_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x15_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x15_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x15_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x16_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x16_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x16_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x17_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x17_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x17_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x18_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x18_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x18_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x19_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x19_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x19_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x20_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x20_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x20_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x21_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x21_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x21_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x22_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x22_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x22_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x23_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x23_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x23_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x24_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x24_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x24_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x25_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x25_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x25_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x26_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x26_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x26_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x27_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x27_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x27_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x28_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x28_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x28_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x29_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x29_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x29_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x30_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x30_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x30_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x31_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x31_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x31_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x32_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x32_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x32_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x33_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x33_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x33_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x34_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x34_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x34_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x35_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x35_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x35_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x36_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x36_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x36_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x37_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x37_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x37_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x38_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x38_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x38_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x39_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x39_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x39_P2.54mm_Vertical_SMD +Connector_PinSocket_2.54mm:PinSocket_2x40_P2.54mm_Horizontal +Connector_PinSocket_2.54mm:PinSocket_2x40_P2.54mm_Vertical +Connector_PinSocket_2.54mm:PinSocket_2x40_P2.54mm_Vertical_SMD +Connector_RJ:RJ12_Amphenol_54601-x06_Horizontal +Connector_RJ:RJ14_Connfly_DS1133-S4_Horizontal +Connector_RJ:RJ25_Wayconn_MJEA-660X1_Horizontal +Connector_RJ:RJ45_Abracon_ARJP11A-MA_Horizontal +Connector_RJ:RJ45_Amphenol_54602-x08_Horizontal +Connector_RJ:RJ45_Amphenol_RJHSE5380-08 +Connector_RJ:RJ45_Amphenol_RJHSE5380 +Connector_RJ:RJ45_Amphenol_RJHSE538X-02 +Connector_RJ:RJ45_Amphenol_RJHSE538X-04 +Connector_RJ:RJ45_Amphenol_RJHSE538X +Connector_RJ:RJ45_Amphenol_RJMG1BD3B8K1ANR +Connector_RJ:RJ45_Bel_SI-60062-F +Connector_RJ:RJ45_BEL_SS74301-00x_Vertical +Connector_RJ:RJ45_Bel_V895-1001-AW_Vertical +Connector_RJ:RJ45_Cetus_J1B1211CCD_Horizontal +Connector_RJ:RJ45_Connfly_DS1128-09-S8xx-S_Horizontal +Connector_RJ:RJ45_HALO_HFJ11-x2450E-LxxRL_Horizontal +Connector_RJ:RJ45_HALO_HFJ11-x2450ERL_Horizontal +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 +Connector_Samtec:Samtec_FMC_ASP-134486-01_10x40_P1.27mm_Vertical +Connector_Samtec:Samtec_FMC_ASP-134602-01_10x40_P1.27mm_Vertical +Connector_Samtec:Samtec_FMC_ASP-134604-01_4x40_Vertical +Connector_Samtec:Samtec_LSHM-105-xx.x-x-DV-N_2x05_P0.50mm_Vertical +Connector_Samtec:Samtec_LSHM-105-xx.x-x-DV-S_2x05-1SH_P0.50mm_Vertical +Connector_Samtec:Samtec_LSHM-110-xx.x-x-DV-N_2x10_P0.50mm_Vertical +Connector_Samtec:Samtec_LSHM-110-xx.x-x-DV-S_2x10-1SH_P0.50mm_Vertical +Connector_Samtec:Samtec_LSHM-120-xx.x-x-DV-N_2x20_P0.50mm_Vertical +Connector_Samtec:Samtec_LSHM-120-xx.x-x-DV-S_2x20-1SH_P0.50mm_Vertical +Connector_Samtec:Samtec_LSHM-130-xx.x-x-DV-N_2x30_P0.50mm_Vertical +Connector_Samtec:Samtec_LSHM-130-xx.x-x-DV-S_2x30-1SH_P0.50mm_Vertical +Connector_Samtec:Samtec_LSHM-140-xx.x-x-DV-N_2x40_P0.50mm_Vertical +Connector_Samtec:Samtec_LSHM-140-xx.x-x-DV-S_2x40-1SH_P0.50mm_Vertical +Connector_Samtec:Samtec_LSHM-150-xx.x-x-DV-N_2x50_P0.50mm_Vertical +Connector_Samtec:Samtec_LSHM-150-xx.x-x-DV-S_2x50-1SH_P0.50mm_Vertical +Connector_Samtec_HLE_SMD:Samtec_HLE-102-02-xxx-DV-BE-LC_2x02_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-102-02-xxx-DV-BE_2x02_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-102-02-xxx-DV-LC_2x02_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-102-02-xxx-DV_2x02_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-103-02-xxx-DV-BE-LC_2x03_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-103-02-xxx-DV-BE_2x03_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-103-02-xxx-DV-LC_2x03_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-103-02-xxx-DV_2x03_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-104-02-xxx-DV-A_2x04_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-104-02-xxx-DV-BE-A_2x04_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-104-02-xxx-DV-BE-LC_2x04_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-104-02-xxx-DV-BE_2x04_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-104-02-xxx-DV-LC_2x04_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-104-02-xxx-DV_2x04_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-105-02-xxx-DV-A_2x05_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-105-02-xxx-DV-BE-A_2x05_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-105-02-xxx-DV-BE-LC_2x05_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-105-02-xxx-DV-BE_2x05_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-105-02-xxx-DV-LC_2x05_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-105-02-xxx-DV_2x05_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-106-02-xxx-DV-A_2x06_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-106-02-xxx-DV-BE-A_2x06_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-106-02-xxx-DV-BE-LC_2x06_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-106-02-xxx-DV-BE_2x06_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-106-02-xxx-DV-LC_2x06_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-106-02-xxx-DV_2x06_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-107-02-xxx-DV-A_2x07_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-107-02-xxx-DV-BE-A_2x07_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-107-02-xxx-DV-BE-LC_2x07_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-107-02-xxx-DV-BE_2x07_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-107-02-xxx-DV-LC_2x07_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-107-02-xxx-DV_2x07_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-108-02-xxx-DV-A_2x08_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-108-02-xxx-DV-BE-A_2x08_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-108-02-xxx-DV-BE-LC_2x08_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-108-02-xxx-DV-BE_2x08_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-108-02-xxx-DV-LC_2x08_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-108-02-xxx-DV_2x08_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-109-02-xxx-DV-A_2x09_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-109-02-xxx-DV-BE-A_2x09_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-109-02-xxx-DV-BE-LC_2x09_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-109-02-xxx-DV-BE_2x09_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-109-02-xxx-DV-LC_2x09_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-109-02-xxx-DV_2x09_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-110-02-xxx-DV-A_2x10_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-110-02-xxx-DV-BE-A_2x10_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-110-02-xxx-DV-BE-LC_2x10_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-110-02-xxx-DV-BE_2x10_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-110-02-xxx-DV-LC_2x10_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-110-02-xxx-DV_2x10_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-111-02-xxx-DV-A_2x11_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-111-02-xxx-DV-BE-A_2x11_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-111-02-xxx-DV-BE-LC_2x11_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-111-02-xxx-DV-BE_2x11_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-111-02-xxx-DV-LC_2x11_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-111-02-xxx-DV_2x11_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-112-02-xxx-DV-A_2x12_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-112-02-xxx-DV-BE-A_2x12_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-112-02-xxx-DV-BE-LC_2x12_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-112-02-xxx-DV-BE_2x12_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-112-02-xxx-DV-LC_2x12_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-112-02-xxx-DV_2x12_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-113-02-xxx-DV-A_2x13_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-113-02-xxx-DV-BE-A_2x13_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-113-02-xxx-DV-BE-LC_2x13_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-113-02-xxx-DV-BE_2x13_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-113-02-xxx-DV-LC_2x13_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-113-02-xxx-DV_2x13_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-114-02-xxx-DV-A_2x14_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-114-02-xxx-DV-BE-A_2x14_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-114-02-xxx-DV-BE-LC_2x14_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-114-02-xxx-DV-BE_2x14_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-114-02-xxx-DV-LC_2x14_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-114-02-xxx-DV_2x14_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-115-02-xxx-DV-A_2x15_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-115-02-xxx-DV-BE-A_2x15_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-115-02-xxx-DV-BE-LC_2x15_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-115-02-xxx-DV-BE_2x15_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-115-02-xxx-DV-LC_2x15_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-115-02-xxx-DV_2x15_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-116-02-xxx-DV-A_2x16_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-116-02-xxx-DV-BE-A_2x16_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-116-02-xxx-DV-BE-LC_2x16_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-116-02-xxx-DV-BE_2x16_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-116-02-xxx-DV-LC_2x16_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-116-02-xxx-DV_2x16_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-117-02-xxx-DV-A_2x17_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-117-02-xxx-DV-BE-A_2x17_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-117-02-xxx-DV-BE-LC_2x17_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-117-02-xxx-DV-BE_2x17_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-117-02-xxx-DV-LC_2x17_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-117-02-xxx-DV_2x17_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-118-02-xxx-DV-A_2x18_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-118-02-xxx-DV-BE-A_2x18_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-118-02-xxx-DV-BE-LC_2x18_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-118-02-xxx-DV-BE_2x18_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-118-02-xxx-DV-LC_2x18_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-118-02-xxx-DV_2x18_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-119-02-xxx-DV-A_2x19_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-119-02-xxx-DV-BE-A_2x19_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-119-02-xxx-DV-BE-LC_2x19_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-119-02-xxx-DV-BE_2x19_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-119-02-xxx-DV-LC_2x19_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-119-02-xxx-DV_2x19_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-120-02-xxx-DV-A_2x20_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-120-02-xxx-DV-BE-A_2x20_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-120-02-xxx-DV-BE-LC_2x20_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-120-02-xxx-DV-BE_2x20_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-120-02-xxx-DV-LC_2x20_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-120-02-xxx-DV_2x20_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-121-02-xxx-DV-A_2x21_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-121-02-xxx-DV-BE-A_2x21_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-121-02-xxx-DV-BE-LC_2x21_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-121-02-xxx-DV-BE_2x21_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-121-02-xxx-DV-LC_2x21_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-121-02-xxx-DV_2x21_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-122-02-xxx-DV-A_2x22_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-122-02-xxx-DV-BE-A_2x22_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-122-02-xxx-DV-BE-LC_2x22_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-122-02-xxx-DV-BE_2x22_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-122-02-xxx-DV-LC_2x22_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-122-02-xxx-DV_2x22_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-123-02-xxx-DV-A_2x23_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-123-02-xxx-DV-BE-A_2x23_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-123-02-xxx-DV-BE-LC_2x23_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-123-02-xxx-DV-BE_2x23_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-123-02-xxx-DV-LC_2x23_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-123-02-xxx-DV_2x23_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-124-02-xxx-DV-A_2x24_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-124-02-xxx-DV-BE-A_2x24_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-124-02-xxx-DV-BE-LC_2x24_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-124-02-xxx-DV-BE_2x24_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-124-02-xxx-DV-LC_2x24_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-124-02-xxx-DV_2x24_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-125-02-xxx-DV-A_2x25_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-125-02-xxx-DV-BE-A_2x25_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-125-02-xxx-DV-BE-LC_2x25_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-125-02-xxx-DV-BE_2x25_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-125-02-xxx-DV-LC_2x25_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-125-02-xxx-DV_2x25_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-126-02-xxx-DV-A_2x26_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-126-02-xxx-DV-BE-A_2x26_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-126-02-xxx-DV-BE-LC_2x26_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-126-02-xxx-DV-BE_2x26_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-126-02-xxx-DV-LC_2x26_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-126-02-xxx-DV_2x26_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-127-02-xxx-DV-A_2x27_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-127-02-xxx-DV-BE-A_2x27_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-127-02-xxx-DV-BE-LC_2x27_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-127-02-xxx-DV-BE_2x27_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-127-02-xxx-DV-LC_2x27_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-127-02-xxx-DV_2x27_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-128-02-xxx-DV-A_2x28_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-128-02-xxx-DV-BE-A_2x28_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-128-02-xxx-DV-BE-LC_2x28_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-128-02-xxx-DV-BE_2x28_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-128-02-xxx-DV-LC_2x28_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-128-02-xxx-DV_2x28_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-129-02-xxx-DV-A_2x29_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-129-02-xxx-DV-BE-A_2x29_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-129-02-xxx-DV-BE-LC_2x29_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-129-02-xxx-DV-BE_2x29_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-129-02-xxx-DV-LC_2x29_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-129-02-xxx-DV_2x29_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-130-02-xxx-DV-A_2x30_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-130-02-xxx-DV-BE-A_2x30_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-130-02-xxx-DV-BE-LC_2x30_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-130-02-xxx-DV-BE_2x30_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-130-02-xxx-DV-LC_2x30_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-130-02-xxx-DV_2x30_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-131-02-xxx-DV-A_2x31_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-131-02-xxx-DV-BE-A_2x31_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-131-02-xxx-DV-BE-LC_2x31_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-131-02-xxx-DV-BE_2x31_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-131-02-xxx-DV-LC_2x31_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-131-02-xxx-DV_2x31_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-132-02-xxx-DV-A_2x32_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-132-02-xxx-DV-BE-A_2x32_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-132-02-xxx-DV-BE-LC_2x32_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-132-02-xxx-DV-BE_2x32_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-132-02-xxx-DV-LC_2x32_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-132-02-xxx-DV_2x32_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-133-02-xxx-DV-A_2x33_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-133-02-xxx-DV-BE-A_2x33_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-133-02-xxx-DV-BE-LC_2x33_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-133-02-xxx-DV-BE_2x33_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-133-02-xxx-DV-LC_2x33_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-133-02-xxx-DV_2x33_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-134-02-xxx-DV-A_2x34_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-134-02-xxx-DV-BE-A_2x34_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-134-02-xxx-DV-BE-LC_2x34_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-134-02-xxx-DV-BE_2x34_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-134-02-xxx-DV-LC_2x34_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-134-02-xxx-DV_2x34_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-135-02-xxx-DV-A_2x35_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-135-02-xxx-DV-BE-A_2x35_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-135-02-xxx-DV-BE-LC_2x35_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-135-02-xxx-DV-BE_2x35_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-135-02-xxx-DV-LC_2x35_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-135-02-xxx-DV_2x35_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-136-02-xxx-DV-A_2x36_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-136-02-xxx-DV-BE-A_2x36_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-136-02-xxx-DV-BE-LC_2x36_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-136-02-xxx-DV-BE_2x36_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-136-02-xxx-DV-LC_2x36_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-136-02-xxx-DV_2x36_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-137-02-xxx-DV-A_2x37_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-137-02-xxx-DV-BE-A_2x37_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-137-02-xxx-DV-BE-LC_2x37_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-137-02-xxx-DV-BE_2x37_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-137-02-xxx-DV-LC_2x37_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-137-02-xxx-DV_2x37_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-138-02-xxx-DV-A_2x38_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-138-02-xxx-DV-BE-A_2x38_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-138-02-xxx-DV-BE-LC_2x38_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-138-02-xxx-DV-BE_2x38_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-138-02-xxx-DV-LC_2x38_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-138-02-xxx-DV_2x38_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-139-02-xxx-DV-A_2x39_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-139-02-xxx-DV-BE-A_2x39_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-139-02-xxx-DV-BE-LC_2x39_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-139-02-xxx-DV-BE_2x39_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-139-02-xxx-DV-LC_2x39_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-139-02-xxx-DV_2x39_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-140-02-xxx-DV-A_2x40_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-140-02-xxx-DV-BE-A_2x40_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-140-02-xxx-DV-BE-LC_2x40_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-140-02-xxx-DV-BE_2x40_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-140-02-xxx-DV-LC_2x40_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-140-02-xxx-DV_2x40_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-141-02-xxx-DV-A_2x41_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-141-02-xxx-DV-BE-A_2x41_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-141-02-xxx-DV-BE-LC_2x41_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-141-02-xxx-DV-BE_2x41_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-141-02-xxx-DV-LC_2x41_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-141-02-xxx-DV_2x41_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-142-02-xxx-DV-A_2x42_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-142-02-xxx-DV-BE-A_2x42_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-142-02-xxx-DV-BE-LC_2x42_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-142-02-xxx-DV-BE_2x42_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-142-02-xxx-DV-LC_2x42_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-142-02-xxx-DV_2x42_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-143-02-xxx-DV-A_2x43_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-143-02-xxx-DV-BE-A_2x43_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-143-02-xxx-DV-BE-LC_2x43_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-143-02-xxx-DV-BE_2x43_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-143-02-xxx-DV-LC_2x43_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-143-02-xxx-DV_2x43_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-144-02-xxx-DV-A_2x44_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-144-02-xxx-DV-BE-A_2x44_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-144-02-xxx-DV-BE-LC_2x44_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-144-02-xxx-DV-BE_2x44_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-144-02-xxx-DV-LC_2x44_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-144-02-xxx-DV_2x44_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-145-02-xxx-DV-A_2x45_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-145-02-xxx-DV-BE-A_2x45_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-145-02-xxx-DV-BE-LC_2x45_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-145-02-xxx-DV-BE_2x45_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-145-02-xxx-DV-LC_2x45_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-145-02-xxx-DV_2x45_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-146-02-xxx-DV-A_2x46_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-146-02-xxx-DV-BE-A_2x46_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-146-02-xxx-DV-BE-LC_2x46_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-146-02-xxx-DV-BE_2x46_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-146-02-xxx-DV-LC_2x46_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-146-02-xxx-DV_2x46_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-147-02-xxx-DV-A_2x47_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-147-02-xxx-DV-BE-A_2x47_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-147-02-xxx-DV-BE-LC_2x47_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-147-02-xxx-DV-BE_2x47_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-147-02-xxx-DV-LC_2x47_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-147-02-xxx-DV_2x47_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-148-02-xxx-DV-A_2x48_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-148-02-xxx-DV-BE-A_2x48_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-148-02-xxx-DV-BE-LC_2x48_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-148-02-xxx-DV-BE_2x48_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-148-02-xxx-DV-LC_2x48_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-148-02-xxx-DV_2x48_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-149-02-xxx-DV-A_2x49_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-149-02-xxx-DV-BE-A_2x49_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-149-02-xxx-DV-BE-LC_2x49_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-149-02-xxx-DV-BE_2x49_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-149-02-xxx-DV-LC_2x49_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-149-02-xxx-DV_2x49_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-150-02-xxx-DV-A_2x50_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-150-02-xxx-DV-BE-A_2x50_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-150-02-xxx-DV-BE-LC_2x50_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-150-02-xxx-DV-BE_2x50_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-150-02-xxx-DV-LC_2x50_P2.54mm_Horizontal +Connector_Samtec_HLE_SMD:Samtec_HLE-150-02-xxx-DV_2x50_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-104-02-xx-DV-PE-LC_2x04_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-104-02-xx-DV-PE_2x04_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-104-02-xx-DV-TE_2x04_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-105-02-xx-DV-PE-LC_2x05_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-105-02-xx-DV-PE_2x05_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-105-02-xx-DV-TE_2x05_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-106-02-xx-DV-PE-LC_2x06_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-106-02-xx-DV-PE_2x06_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-106-02-xx-DV-TE_2x06_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-107-02-xx-DV-PE-LC_2x07_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-107-02-xx-DV-PE_2x07_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-107-02-xx-DV-TE_2x07_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-108-02-xx-DV-PE-LC_2x08_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-108-02-xx-DV-PE_2x08_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-108-02-xx-DV-TE_2x08_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-109-02-xx-DV-PE-LC_2x09_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-109-02-xx-DV-PE_2x09_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-109-02-xx-DV-TE_2x09_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-110-02-xx-DV-PE-LC_2x10_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-110-02-xx-DV-PE_2x10_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-110-02-xx-DV-TE_2x10_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-111-02-xx-DV-PE-LC_2x11_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-111-02-xx-DV-PE_2x11_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-111-02-xx-DV-TE_2x11_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-112-02-xx-DV-PE-LC_2x12_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-112-02-xx-DV-PE_2x12_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-112-02-xx-DV-TE_2x12_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-113-02-xx-DV-PE-LC_2x13_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-113-02-xx-DV-PE_2x13_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-113-02-xx-DV-TE_2x13_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-114-02-xx-DV-PE-LC_2x14_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-114-02-xx-DV-PE_2x14_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-114-02-xx-DV-TE_2x14_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-115-02-xx-DV-PE-LC_2x15_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-115-02-xx-DV-PE_2x15_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-115-02-xx-DV-TE_2x15_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-116-02-xx-DV-PE-LC_2x16_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-116-02-xx-DV-PE_2x16_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-116-02-xx-DV-TE_2x16_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-117-02-xx-DV-PE-LC_2x17_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-117-02-xx-DV-PE_2x17_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-117-02-xx-DV-TE_2x17_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-118-02-xx-DV-PE-LC_2x18_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-118-02-xx-DV-PE_2x18_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-118-02-xx-DV-TE_2x18_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-119-02-xx-DV-PE-LC_2x19_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-119-02-xx-DV-PE_2x19_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-119-02-xx-DV-TE_2x19_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-120-02-xx-DV-PE-LC_2x20_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-120-02-xx-DV-PE_2x20_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-120-02-xx-DV-TE_2x20_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-121-02-xx-DV-PE-LC_2x21_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-121-02-xx-DV-PE_2x21_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-121-02-xx-DV-TE_2x21_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-122-02-xx-DV-PE-LC_2x22_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-122-02-xx-DV-PE_2x22_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-122-02-xx-DV-TE_2x22_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-123-02-xx-DV-PE-LC_2x23_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-123-02-xx-DV-PE_2x23_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-123-02-xx-DV-TE_2x23_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-124-02-xx-DV-PE-LC_2x24_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-124-02-xx-DV-PE_2x24_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-124-02-xx-DV-TE_2x24_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-125-02-xx-DV-PE-LC_2x25_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-125-02-xx-DV-PE_2x25_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-125-02-xx-DV-TE_2x25_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-126-02-xx-DV-PE-LC_2x26_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-126-02-xx-DV-PE_2x26_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-126-02-xx-DV-TE_2x26_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-127-02-xx-DV-PE-LC_2x27_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-127-02-xx-DV-PE_2x27_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-127-02-xx-DV-TE_2x27_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-128-02-xx-DV-PE-LC_2x28_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-128-02-xx-DV-PE_2x28_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-128-02-xx-DV-TE_2x28_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-129-02-xx-DV-PE-LC_2x29_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-129-02-xx-DV-PE_2x29_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-129-02-xx-DV-TE_2x29_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-130-02-xx-DV-PE-LC_2x30_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-130-02-xx-DV-PE_2x30_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-130-02-xx-DV-TE_2x30_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-131-02-xx-DV-PE-LC_2x31_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-131-02-xx-DV-PE_2x31_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-131-02-xx-DV-TE_2x31_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-132-02-xx-DV-PE-LC_2x32_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-132-02-xx-DV-PE_2x32_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-132-02-xx-DV-TE_2x32_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-133-02-xx-DV-PE-LC_2x33_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-133-02-xx-DV-PE_2x33_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-133-02-xx-DV-TE_2x33_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-134-02-xx-DV-PE-LC_2x34_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-134-02-xx-DV-PE_2x34_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-134-02-xx-DV-TE_2x34_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-135-02-xx-DV-PE-LC_2x35_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-135-02-xx-DV-PE_2x35_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-135-02-xx-DV-TE_2x35_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-136-02-xx-DV-PE-LC_2x36_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-136-02-xx-DV-PE_2x36_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-136-02-xx-DV-TE_2x36_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-137-02-xx-DV-PE-LC_2x37_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-137-02-xx-DV-PE_2x37_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-137-02-xx-DV-TE_2x37_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-138-02-xx-DV-PE-LC_2x38_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-138-02-xx-DV-PE_2x38_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-138-02-xx-DV-TE_2x38_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-139-02-xx-DV-PE-LC_2x39_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-139-02-xx-DV-PE_2x39_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-139-02-xx-DV-TE_2x39_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-140-02-xx-DV-PE-LC_2x40_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-140-02-xx-DV-PE_2x40_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-140-02-xx-DV-TE_2x40_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-141-02-xx-DV-PE-LC_2x41_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-141-02-xx-DV-PE_2x41_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-141-02-xx-DV-TE_2x41_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-142-02-xx-DV-PE-LC_2x42_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-142-02-xx-DV-PE_2x42_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-142-02-xx-DV-TE_2x42_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-143-02-xx-DV-PE-LC_2x43_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-143-02-xx-DV-PE_2x43_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-143-02-xx-DV-TE_2x43_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-144-02-xx-DV-PE-LC_2x44_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-144-02-xx-DV-PE_2x44_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-144-02-xx-DV-TE_2x44_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-145-02-xx-DV-PE-LC_2x45_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-145-02-xx-DV-PE_2x45_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-145-02-xx-DV-TE_2x45_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-146-02-xx-DV-PE-LC_2x46_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-146-02-xx-DV-PE_2x46_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-146-02-xx-DV-TE_2x46_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-147-02-xx-DV-PE-LC_2x47_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-147-02-xx-DV-PE_2x47_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-147-02-xx-DV-TE_2x47_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-148-02-xx-DV-PE-LC_2x48_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-148-02-xx-DV-PE_2x48_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-148-02-xx-DV-TE_2x48_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-149-02-xx-DV-PE-LC_2x49_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-149-02-xx-DV-PE_2x49_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-149-02-xx-DV-TE_2x49_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-150-02-xx-DV-PE-LC_2x50_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-150-02-xx-DV-PE_2x50_P2.54mm_Horizontal +Connector_Samtec_HLE_THT:Samtec_HLE-150-02-xx-DV-TE_2x50_P2.54mm_Horizontal +Connector_Samtec_HPM_THT:Samtec_HPM-01-01-x-S_Straight_1x01_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-01-05-x-S_Straight_1x01_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-02-01-x-S_Straight_1x02_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-02-05-x-S_Straight_1x02_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-03-01-x-S_Straight_1x03_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-03-05-x-S_Straight_1x03_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-04-01-x-S_Straight_1x04_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-04-05-x-S_Straight_1x04_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-05-01-x-S_Straight_1x05_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-05-05-x-S_Straight_1x05_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-06-01-x-S_Straight_1x06_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-06-05-x-S_Straight_1x06_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-07-01-x-S_Straight_1x07_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-07-05-x-S_Straight_1x07_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-08-01-x-S_Straight_1x08_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-08-05-x-S_Straight_1x08_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-09-01-x-S_Straight_1x09_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-09-05-x-S_Straight_1x09_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-10-01-x-S_Straight_1x10_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-10-05-x-S_Straight_1x10_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-11-01-x-S_Straight_1x11_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-11-05-x-S_Straight_1x11_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-12-01-x-S_Straight_1x12_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-12-05-x-S_Straight_1x12_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-13-01-x-S_Straight_1x13_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-13-05-x-S_Straight_1x13_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-14-01-x-S_Straight_1x14_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-14-05-x-S_Straight_1x14_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-15-01-x-S_Straight_1x15_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-15-05-x-S_Straight_1x15_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-16-01-x-S_Straight_1x16_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-16-05-x-S_Straight_1x16_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-17-01-x-S_Straight_1x17_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-17-05-x-S_Straight_1x17_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-18-01-x-S_Straight_1x18_Pitch5.08mm +Connector_Samtec_HPM_THT:Samtec_HPM-18-05-x-S_Straight_1x18_Pitch5.08mm +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-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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +Connector_Samtec_MicroMate:Samtec_T1M-02-X-SV-L_1x02-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-03-X-S-RA_1x03-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-03-X-S-V_1x03-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-03-X-SH-L_1x03-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-03-X-SV-L_1x03-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-04-X-S-RA_1x04-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-04-X-S-V_1x04-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-04-X-SH-L_1x04-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-04-X-SV-L_1x04-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-05-X-S-RA_1x05-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-05-X-S-V_1x05-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-05-X-SH-L_1x05-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-05-X-SV-L_1x05-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-06-X-S-RA_1x06-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-06-X-S-V_1x06-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-06-X-SH-L_1x06-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-06-X-SV-L_1x06-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-07-X-S-RA_1x07-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-07-X-S-V_1x07-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-07-X-SH-L_1x07-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-07-X-SV-L_1x07-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-08-X-S-RA_1x08-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-08-X-S-V_1x08-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-08-X-SH-L_1x08-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-08-X-SV-L_1x08-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-09-X-S-RA_1x09-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-09-X-S-V_1x09-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-09-X-SH-L_1x09-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-09-X-SV-L_1x09-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-10-X-S-RA_1x10-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-10-X-S-V_1x10-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-10-X-SH-L_1x10-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-10-X-SV-L_1x10-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-11-X-S-RA_1x11-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-11-X-S-V_1x11-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-11-X-SH-L_1x11-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-11-X-SV-L_1x11-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-12-X-S-RA_1x12-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-12-X-S-V_1x12-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-12-X-SH-L_1x12-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-12-X-SV-L_1x12-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-13-X-S-RA_1x13-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-13-X-S-V_1x13-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-13-X-SH-L_1x13-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-13-X-SV-L_1x13-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-14-X-S-RA_1x14-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-14-X-S-V_1x14-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-14-X-SH-L_1x14-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-14-X-SV-L_1x14-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-15-X-S-RA_1x15-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-15-X-S-V_1x15-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-15-X-SH-L_1x15-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-15-X-SV-L_1x15-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-16-X-S-RA_1x16-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-16-X-S-V_1x16-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-16-X-SH-L_1x16-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-16-X-SV-L_1x16-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-17-X-S-RA_1x17-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-17-X-S-V_1x17-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-17-X-SH-L_1x17-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-17-X-SV-L_1x17-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-18-X-S-RA_1x18-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-18-X-S-V_1x18-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-18-X-SH-L_1x18-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-18-X-SV-L_1x18-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-19-X-S-RA_1x19-1MP_P1.0mm_Terminal_Horizontal +Connector_Samtec_MicroMate:Samtec_T1M-19-X-S-V_1x19-1MP_P1.0mm_Terminal_Vertical +Connector_Samtec_MicroMate:Samtec_T1M-19-X-SH-L_1x19-1MP_P1.0mm_Terminal_Horizontal_Latch +Connector_Samtec_MicroMate:Samtec_T1M-19-X-SV-L_1x19-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroMate:Samtec_T1M-20-X-S-RA_1x20-1MP_P1.0mm_Terminal_Horizontal +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 +Connector_Stocko:Stocko_MKS_1652-6-0-202_1x2_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1653-6-0-303_1x3_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1654-6-0-404_1x4_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1655-6-0-505_1x5_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1656-6-0-606_1x6_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1657-6-0-707_1x7_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1658-6-0-808_1x8_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1659-6-0-909_1x9_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1660-6-0-1010_1x10_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1661-6-0-1111_1x11_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1662-6-0-1212_1x12_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1663-6-0-1313_1x13_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1664-6-0-1414_1x14_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1665-6-0-1515_1x15_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1666-6-0-1616_1x16_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1667-6-0-1717_1x17_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1668-6-0-1818_1x18_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1669-6-0-1919_1x19_P2.50mm_Vertical +Connector_Stocko:Stocko_MKS_1670-6-0-2020_1x20_P2.50mm_Vertical +Connector_TE-Connectivity:TE_1-826576-3_1x13_P3.96mm_Vertical +Connector_TE-Connectivity:TE_1-826576-5_1x15_P3.96mm_Vertical +Connector_TE-Connectivity:TE_1-826576-6_1x16_P3.96mm_Vertical +Connector_TE-Connectivity:TE_1-826576-7_1x17_P3.96mm_Vertical +Connector_TE-Connectivity:TE_1-826576-8_1x18_P3.96mm_Vertical +Connector_TE-Connectivity:TE_2-826576-0_1x20_P3.96mm_Vertical +Connector_TE-Connectivity:TE_2834006-1_1x01_P4.0mm_Horizontal +Connector_TE-Connectivity:TE_2834006-2_1x02_P4.0mm_Horizontal +Connector_TE-Connectivity:TE_2834006-3_1x03_P4.0mm_Horizontal +Connector_TE-Connectivity:TE_2834006-4_1x04_P4.0mm_Horizontal +Connector_TE-Connectivity:TE_2834006-5_1x05_P4.0mm_Horizontal +Connector_TE-Connectivity:TE_3-826576-6_1x36_P3.96mm_Vertical +Connector_TE-Connectivity:TE_440054-2_1x02_P2.00mm_Vertical +Connector_TE-Connectivity:TE_440055-2_1x02_P2.00mm_Horizontal +Connector_TE-Connectivity:TE_5767171-1_2x19_P0.635mm_Vertical +Connector_TE-Connectivity:TE_826576-2_1x02_P3.96mm_Vertical +Connector_TE-Connectivity:TE_826576-3_1x03_P3.96mm_Vertical +Connector_TE-Connectivity:TE_826576-5_1x05_P3.96mm_Vertical +Connector_TE-Connectivity:TE_826576-6_1x06_P3.96mm_Vertical +Connector_TE-Connectivity:TE_826576-7_1x07_P3.96mm_Vertical +Connector_TE-Connectivity:TE_826576-8_1x08_P3.96mm_Vertical +Connector_TE-Connectivity:TE_826576-9_1x09_P3.96mm_Vertical +Connector_TE-Connectivity:TE_AMPSEAL_1-776087-x_3Rows_23_P0.4mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770182-x_3x03_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770186-x_3x04_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770190-x_3x05_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770621-x_2x06_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770858-x_2x05_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770866-x_1x02_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770870-x_1x03_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770874-x_2x02_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770875-x_2x03_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770966-x_1x02_P4.14mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770967-x_1x03_P4.14mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770968-x_2x02_P4.14mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770969-x_2x03_P4.14mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770970-x_2x04_P4.14mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770971-x_2x05_P4.14mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770972-x_2x06_P4.14mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770973-x_2x07_P4.14mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_1-770974-x_2x08_P4.14mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_1-794067-x_2x07_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-794068-x_2x08_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-794069-x_2x09_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-794070-x_2x10_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-794071-x_2x11_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-794072-x_2x12_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-794073-x_2x04_P4.14mm_Vertical +Connector_TE-Connectivity:TE_MATE-N-LOK_1-794105-x_2x09_P4.14mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_1-794106-x_2x10_P4.14mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_1-794107-x_2x11_P4.14mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_1-794108-x_2x12_P4.14mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_1-794374-x_1x01_P4.14mm_Horizontal +Connector_TE-Connectivity:TE_MATE-N-LOK_350211-1_1x04_P5.08mm_Vertical +Connector_TE-Connectivity:TE_Micro-MaTch_1-215079-0_2x05_P1.27mm_Vertical +Connector_TE-Connectivity:TE_Micro-MaTch_1-215079-2_2x06_P1.27mm_Vertical +Connector_TE-Connectivity:TE_Micro-MaTch_1-215079-4_2x07_P1.27mm_Vertical +Connector_TE-Connectivity:TE_Micro-MaTch_1-215079-6_2x08_P1.27mm_Vertical +Connector_TE-Connectivity:TE_Micro-MaTch_1-215079-8_2x09_P1.27mm_Vertical +Connector_TE-Connectivity:TE_Micro-MaTch_2-215079-0_2x10_P1.27mm_Vertical +Connector_TE-Connectivity:TE_Micro-MaTch_215079-4_2x02_P1.27mm_Vertical +Connector_TE-Connectivity:TE_Micro-MaTch_215079-6_2x03_P1.27mm_Vertical +Connector_TE-Connectivity:TE_Micro-MaTch_215079-8_2x04_P1.27mm_Vertical +Connector_TE-Connectivity:TE_T4041037031-000_M8_03_Socket_Straight +Connector_TE-Connectivity:TE_T4041037041-000_M8_04_Socket_Straight +Connector_USB:USB3_A_Molex_48393-001 +Connector_USB:USB3_A_Molex_48406-0001_Horizontal_Stacked +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 +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 +Connector_USB:USB_A_Wuerth_61400826021_Horizontal_Stacked +Connector_USB:USB_B_Amphenol_MUSB-D511_Vertical_Rugged +Connector_USB:USB_B_Lumberg_2411_02_Horizontal +Connector_USB:USB_B_OST_USB-B1HSxx_Horizontal +Connector_USB:USB_B_TE_5787834_Vertical +Connector_USB:USB_C_Plug_JAE_DX07P024AJ1 +Connector_USB:USB_C_Plug_Molex_105444 +Connector_USB:USB_C_Plug_ShenzhenJingTuoJin_918-118A2021Y40002_Vertical +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 +Connector_USB:USB_C_Receptacle_GCT_USB4110 +Connector_USB:USB_C_Receptacle_GCT_USB4115-03-C +Connector_USB:USB_C_Receptacle_GCT_USB4125-xx-x-0190_6P_TopMnt_Horizontal +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 +Connector_USB:USB_C_Receptacle_Molex_105450-0101 +Connector_USB:USB_C_Receptacle_Palconn_UTC16-G +Connector_USB:USB_C_Receptacle_XKB_U262-16XN-4BVC11 +Connector_USB:USB_Micro-AB_Molex_47590-0001 +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 +Connector_USB:USB_Micro-B_Molex-105133-0001 +Connector_USB:USB_Micro-B_Molex-105133-0031 +Connector_USB:USB_Micro-B_Molex_47346-0001 +Connector_USB:USB_Micro-B_Technik_TWP-4002D-H3 +Connector_USB:USB_Micro-B_Wuerth_614105150721_Vertical +Connector_USB:USB_Micro-B_Wuerth_614105150721_Vertical_CircularHoles +Connector_USB:USB_Micro-B_Wuerth_629105150521 +Connector_USB:USB_Micro-B_Wuerth_629105150521_CircularHoles +Connector_USB:USB_Micro-B_XKB_U254-051T-4BH83-F1S +Connector_USB:USB_Mini-B_AdamTech_MUSB-B5-S-VT-TSMT-1_SMD_Vertical +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 +Connector_Wago:Wago_734-135_1x05_P3.50mm_Vertical +Connector_Wago:Wago_734-136_1x06_P3.50mm_Vertical +Connector_Wago:Wago_734-137_1x07_P3.50mm_Vertical +Connector_Wago:Wago_734-138_1x08_P3.50mm_Vertical +Connector_Wago:Wago_734-139_1x09_P3.50mm_Vertical +Connector_Wago:Wago_734-140_1x10_P3.50mm_Vertical +Connector_Wago:Wago_734-141_1x11_P3.50mm_Vertical +Connector_Wago:Wago_734-142_1x12_P3.50mm_Vertical +Connector_Wago:Wago_734-143_1x13_P3.50mm_Vertical +Connector_Wago:Wago_734-144_1x14_P3.50mm_Vertical +Connector_Wago:Wago_734-146_1x16_P3.50mm_Vertical +Connector_Wago:Wago_734-148_1x18_P3.50mm_Vertical +Connector_Wago:Wago_734-150_1x20_P3.50mm_Vertical +Connector_Wago:Wago_734-154_1x24_P3.50mm_Vertical +Connector_Wago:Wago_734-162_1x02_P3.50mm_Horizontal +Connector_Wago:Wago_734-163_1x03_P3.50mm_Horizontal +Connector_Wago:Wago_734-164_1x04_P3.50mm_Horizontal +Connector_Wago:Wago_734-165_1x05_P3.50mm_Horizontal +Connector_Wago:Wago_734-166_1x06_P3.50mm_Horizontal +Connector_Wago:Wago_734-167_1x07_P3.50mm_Horizontal +Connector_Wago:Wago_734-168_1x08_P3.50mm_Horizontal +Connector_Wago:Wago_734-169_1x09_P3.50mm_Horizontal +Connector_Wago:Wago_734-170_1x10_P3.50mm_Horizontal +Connector_Wago:Wago_734-171_1x11_P3.50mm_Horizontal +Connector_Wago:Wago_734-172_1x12_P3.50mm_Horizontal +Connector_Wago:Wago_734-173_1x13_P3.50mm_Horizontal +Connector_Wago:Wago_734-174_1x14_P3.50mm_Horizontal +Connector_Wago:Wago_734-176_1x16_P3.50mm_Horizontal +Connector_Wago:Wago_734-178_1x18_P3.50mm_Horizontal +Connector_Wago:Wago_734-180_1x20_P3.50mm_Horizontal +Connector_Wago:Wago_734-184_1x24_P3.50mm_Horizontal +Connector_Wire:SolderWire-0.127sqmm_1x01_D0.48mm_OD1mm +Connector_Wire:SolderWire-0.127sqmm_1x01_D0.48mm_OD1mm_Relief +Connector_Wire:SolderWire-0.127sqmm_1x01_D0.48mm_OD1mm_Relief2x +Connector_Wire:SolderWire-0.127sqmm_1x02_P3.7mm_D0.48mm_OD1mm +Connector_Wire:SolderWire-0.127sqmm_1x02_P3.7mm_D0.48mm_OD1mm_Relief +Connector_Wire:SolderWire-0.127sqmm_1x02_P3.7mm_D0.48mm_OD1mm_Relief2x +Connector_Wire:SolderWire-0.127sqmm_1x03_P3.7mm_D0.48mm_OD1mm +Connector_Wire:SolderWire-0.127sqmm_1x03_P3.7mm_D0.48mm_OD1mm_Relief +Connector_Wire:SolderWire-0.127sqmm_1x03_P3.7mm_D0.48mm_OD1mm_Relief2x +Connector_Wire:SolderWire-0.127sqmm_1x04_P3.7mm_D0.48mm_OD1mm +Connector_Wire:SolderWire-0.127sqmm_1x04_P3.7mm_D0.48mm_OD1mm_Relief +Connector_Wire:SolderWire-0.127sqmm_1x04_P3.7mm_D0.48mm_OD1mm_Relief2x +Connector_Wire:SolderWire-0.127sqmm_1x05_P3.7mm_D0.48mm_OD1mm +Connector_Wire:SolderWire-0.127sqmm_1x05_P3.7mm_D0.48mm_OD1mm_Relief +Connector_Wire:SolderWire-0.127sqmm_1x05_P3.7mm_D0.48mm_OD1mm_Relief2x +Connector_Wire:SolderWire-0.127sqmm_1x06_P3.7mm_D0.48mm_OD1mm +Connector_Wire:SolderWire-0.127sqmm_1x06_P3.7mm_D0.48mm_OD1mm_Relief +Connector_Wire:SolderWire-0.127sqmm_1x06_P3.7mm_D0.48mm_OD1mm_Relief2x +Connector_Wire:SolderWire-0.15sqmm_1x01_D0.5mm_OD1.5mm +Connector_Wire:SolderWire-0.15sqmm_1x01_D0.5mm_OD1.5mm_Relief +Connector_Wire:SolderWire-0.15sqmm_1x01_D0.5mm_OD1.5mm_Relief2x +Connector_Wire:SolderWire-0.15sqmm_1x02_P4mm_D0.5mm_OD1.5mm +Connector_Wire:SolderWire-0.15sqmm_1x02_P4mm_D0.5mm_OD1.5mm_Relief +Connector_Wire:SolderWire-0.15sqmm_1x02_P4mm_D0.5mm_OD1.5mm_Relief2x +Connector_Wire:SolderWire-0.15sqmm_1x03_P4mm_D0.5mm_OD1.5mm +Connector_Wire:SolderWire-0.15sqmm_1x03_P4mm_D0.5mm_OD1.5mm_Relief +Connector_Wire:SolderWire-0.15sqmm_1x03_P4mm_D0.5mm_OD1.5mm_Relief2x +Connector_Wire:SolderWire-0.15sqmm_1x04_P4mm_D0.5mm_OD1.5mm +Connector_Wire:SolderWire-0.15sqmm_1x04_P4mm_D0.5mm_OD1.5mm_Relief +Connector_Wire:SolderWire-0.15sqmm_1x04_P4mm_D0.5mm_OD1.5mm_Relief2x +Connector_Wire:SolderWire-0.15sqmm_1x05_P4mm_D0.5mm_OD1.5mm +Connector_Wire:SolderWire-0.15sqmm_1x05_P4mm_D0.5mm_OD1.5mm_Relief +Connector_Wire:SolderWire-0.15sqmm_1x05_P4mm_D0.5mm_OD1.5mm_Relief2x +Connector_Wire:SolderWire-0.15sqmm_1x06_P4mm_D0.5mm_OD1.5mm +Connector_Wire:SolderWire-0.15sqmm_1x06_P4mm_D0.5mm_OD1.5mm_Relief +Connector_Wire:SolderWire-0.15sqmm_1x06_P4mm_D0.5mm_OD1.5mm_Relief2x +Connector_Wire:SolderWire-0.1sqmm_1x01_D0.4mm_OD1mm +Connector_Wire:SolderWire-0.1sqmm_1x01_D0.4mm_OD1mm_Relief +Connector_Wire:SolderWire-0.1sqmm_1x01_D0.4mm_OD1mm_Relief2x +Connector_Wire:SolderWire-0.1sqmm_1x02_P3.6mm_D0.4mm_OD1mm +Connector_Wire:SolderWire-0.1sqmm_1x02_P3.6mm_D0.4mm_OD1mm_Relief +Connector_Wire:SolderWire-0.1sqmm_1x02_P3.6mm_D0.4mm_OD1mm_Relief2x +Connector_Wire:SolderWire-0.1sqmm_1x03_P3.6mm_D0.4mm_OD1mm +Connector_Wire:SolderWire-0.1sqmm_1x03_P3.6mm_D0.4mm_OD1mm_Relief +Connector_Wire:SolderWire-0.1sqmm_1x03_P3.6mm_D0.4mm_OD1mm_Relief2x +Connector_Wire:SolderWire-0.1sqmm_1x04_P3.6mm_D0.4mm_OD1mm +Connector_Wire:SolderWire-0.1sqmm_1x04_P3.6mm_D0.4mm_OD1mm_Relief +Connector_Wire:SolderWire-0.1sqmm_1x04_P3.6mm_D0.4mm_OD1mm_Relief2x +Connector_Wire:SolderWire-0.1sqmm_1x05_P3.6mm_D0.4mm_OD1mm +Connector_Wire:SolderWire-0.1sqmm_1x05_P3.6mm_D0.4mm_OD1mm_Relief +Connector_Wire:SolderWire-0.1sqmm_1x05_P3.6mm_D0.4mm_OD1mm_Relief2x +Connector_Wire:SolderWire-0.1sqmm_1x06_P3.6mm_D0.4mm_OD1mm +Connector_Wire:SolderWire-0.1sqmm_1x06_P3.6mm_D0.4mm_OD1mm_Relief +Connector_Wire:SolderWire-0.1sqmm_1x06_P3.6mm_D0.4mm_OD1mm_Relief2x +Connector_Wire:SolderWire-0.25sqmm_1x01_D0.65mm_OD1.7mm +Connector_Wire:SolderWire-0.25sqmm_1x01_D0.65mm_OD1.7mm_Relief +Connector_Wire:SolderWire-0.25sqmm_1x01_D0.65mm_OD1.7mm_Relief2x +Connector_Wire:SolderWire-0.25sqmm_1x01_D0.65mm_OD2mm +Connector_Wire:SolderWire-0.25sqmm_1x01_D0.65mm_OD2mm_Relief +Connector_Wire:SolderWire-0.25sqmm_1x01_D0.65mm_OD2mm_Relief2x +Connector_Wire:SolderWire-0.25sqmm_1x02_P4.2mm_D0.65mm_OD1.7mm +Connector_Wire:SolderWire-0.25sqmm_1x02_P4.2mm_D0.65mm_OD1.7mm_Relief +Connector_Wire:SolderWire-0.25sqmm_1x02_P4.2mm_D0.65mm_OD1.7mm_Relief2x +Connector_Wire:SolderWire-0.25sqmm_1x02_P4.5mm_D0.65mm_OD2mm +Connector_Wire:SolderWire-0.25sqmm_1x02_P4.5mm_D0.65mm_OD2mm_Relief +Connector_Wire:SolderWire-0.25sqmm_1x02_P4.5mm_D0.65mm_OD2mm_Relief2x +Connector_Wire:SolderWire-0.25sqmm_1x03_P4.2mm_D0.65mm_OD1.7mm +Connector_Wire:SolderWire-0.25sqmm_1x03_P4.2mm_D0.65mm_OD1.7mm_Relief +Connector_Wire:SolderWire-0.25sqmm_1x03_P4.2mm_D0.65mm_OD1.7mm_Relief2x +Connector_Wire:SolderWire-0.25sqmm_1x03_P4.5mm_D0.65mm_OD2mm +Connector_Wire:SolderWire-0.25sqmm_1x03_P4.5mm_D0.65mm_OD2mm_Relief +Connector_Wire:SolderWire-0.25sqmm_1x03_P4.5mm_D0.65mm_OD2mm_Relief2x +Connector_Wire:SolderWire-0.25sqmm_1x04_P4.2mm_D0.65mm_OD1.7mm +Connector_Wire:SolderWire-0.25sqmm_1x04_P4.2mm_D0.65mm_OD1.7mm_Relief +Connector_Wire:SolderWire-0.25sqmm_1x04_P4.2mm_D0.65mm_OD1.7mm_Relief2x +Connector_Wire:SolderWire-0.25sqmm_1x04_P4.5mm_D0.65mm_OD2mm +Connector_Wire:SolderWire-0.25sqmm_1x04_P4.5mm_D0.65mm_OD2mm_Relief +Connector_Wire:SolderWire-0.25sqmm_1x04_P4.5mm_D0.65mm_OD2mm_Relief2x +Connector_Wire:SolderWire-0.25sqmm_1x05_P4.2mm_D0.65mm_OD1.7mm +Connector_Wire:SolderWire-0.25sqmm_1x05_P4.2mm_D0.65mm_OD1.7mm_Relief +Connector_Wire:SolderWire-0.25sqmm_1x05_P4.2mm_D0.65mm_OD1.7mm_Relief2x +Connector_Wire:SolderWire-0.25sqmm_1x05_P4.5mm_D0.65mm_OD2mm +Connector_Wire:SolderWire-0.25sqmm_1x05_P4.5mm_D0.65mm_OD2mm_Relief +Connector_Wire:SolderWire-0.25sqmm_1x05_P4.5mm_D0.65mm_OD2mm_Relief2x +Connector_Wire:SolderWire-0.25sqmm_1x06_P4.2mm_D0.65mm_OD1.7mm +Connector_Wire:SolderWire-0.25sqmm_1x06_P4.2mm_D0.65mm_OD1.7mm_Relief +Connector_Wire:SolderWire-0.25sqmm_1x06_P4.2mm_D0.65mm_OD1.7mm_Relief2x +Connector_Wire:SolderWire-0.25sqmm_1x06_P4.5mm_D0.65mm_OD2mm +Connector_Wire:SolderWire-0.25sqmm_1x06_P4.5mm_D0.65mm_OD2mm_Relief +Connector_Wire:SolderWire-0.25sqmm_1x06_P4.5mm_D0.65mm_OD2mm_Relief2x +Connector_Wire:SolderWire-0.5sqmm_1x01_D0.9mm_OD2.1mm +Connector_Wire:SolderWire-0.5sqmm_1x01_D0.9mm_OD2.1mm_Relief +Connector_Wire:SolderWire-0.5sqmm_1x01_D0.9mm_OD2.1mm_Relief2x +Connector_Wire:SolderWire-0.5sqmm_1x01_D0.9mm_OD2.3mm +Connector_Wire:SolderWire-0.5sqmm_1x01_D0.9mm_OD2.3mm_Relief +Connector_Wire:SolderWire-0.5sqmm_1x01_D0.9mm_OD2.3mm_Relief2x +Connector_Wire:SolderWire-0.5sqmm_1x02_P4.6mm_D0.9mm_OD2.1mm +Connector_Wire:SolderWire-0.5sqmm_1x02_P4.6mm_D0.9mm_OD2.1mm_Relief +Connector_Wire:SolderWire-0.5sqmm_1x02_P4.6mm_D0.9mm_OD2.1mm_Relief2x +Connector_Wire:SolderWire-0.5sqmm_1x02_P4.8mm_D0.9mm_OD2.3mm +Connector_Wire:SolderWire-0.5sqmm_1x02_P4.8mm_D0.9mm_OD2.3mm_Relief +Connector_Wire:SolderWire-0.5sqmm_1x02_P4.8mm_D0.9mm_OD2.3mm_Relief2x +Connector_Wire:SolderWire-0.5sqmm_1x03_P4.6mm_D0.9mm_OD2.1mm +Connector_Wire:SolderWire-0.5sqmm_1x03_P4.6mm_D0.9mm_OD2.1mm_Relief +Connector_Wire:SolderWire-0.5sqmm_1x03_P4.6mm_D0.9mm_OD2.1mm_Relief2x +Connector_Wire:SolderWire-0.5sqmm_1x03_P4.8mm_D0.9mm_OD2.3mm +Connector_Wire:SolderWire-0.5sqmm_1x03_P4.8mm_D0.9mm_OD2.3mm_Relief +Connector_Wire:SolderWire-0.5sqmm_1x03_P4.8mm_D0.9mm_OD2.3mm_Relief2x +Connector_Wire:SolderWire-0.5sqmm_1x04_P4.6mm_D0.9mm_OD2.1mm +Connector_Wire:SolderWire-0.5sqmm_1x04_P4.6mm_D0.9mm_OD2.1mm_Relief +Connector_Wire:SolderWire-0.5sqmm_1x04_P4.6mm_D0.9mm_OD2.1mm_Relief2x +Connector_Wire:SolderWire-0.5sqmm_1x04_P4.8mm_D0.9mm_OD2.3mm +Connector_Wire:SolderWire-0.5sqmm_1x04_P4.8mm_D0.9mm_OD2.3mm_Relief +Connector_Wire:SolderWire-0.5sqmm_1x04_P4.8mm_D0.9mm_OD2.3mm_Relief2x +Connector_Wire:SolderWire-0.5sqmm_1x05_P4.6mm_D0.9mm_OD2.1mm +Connector_Wire:SolderWire-0.5sqmm_1x05_P4.6mm_D0.9mm_OD2.1mm_Relief +Connector_Wire:SolderWire-0.5sqmm_1x05_P4.6mm_D0.9mm_OD2.1mm_Relief2x +Connector_Wire:SolderWire-0.5sqmm_1x05_P4.8mm_D0.9mm_OD2.3mm +Connector_Wire:SolderWire-0.5sqmm_1x05_P4.8mm_D0.9mm_OD2.3mm_Relief +Connector_Wire:SolderWire-0.5sqmm_1x05_P4.8mm_D0.9mm_OD2.3mm_Relief2x +Connector_Wire:SolderWire-0.5sqmm_1x06_P4.6mm_D0.9mm_OD2.1mm +Connector_Wire:SolderWire-0.5sqmm_1x06_P4.6mm_D0.9mm_OD2.1mm_Relief +Connector_Wire:SolderWire-0.5sqmm_1x06_P4.6mm_D0.9mm_OD2.1mm_Relief2x +Connector_Wire:SolderWire-0.5sqmm_1x06_P4.8mm_D0.9mm_OD2.3mm +Connector_Wire:SolderWire-0.5sqmm_1x06_P4.8mm_D0.9mm_OD2.3mm_Relief +Connector_Wire:SolderWire-0.5sqmm_1x06_P4.8mm_D0.9mm_OD2.3mm_Relief2x +Connector_Wire:SolderWire-0.75sqmm_1x01_D1.25mm_OD2.3mm +Connector_Wire:SolderWire-0.75sqmm_1x01_D1.25mm_OD2.3mm_Relief +Connector_Wire:SolderWire-0.75sqmm_1x01_D1.25mm_OD2.3mm_Relief2x +Connector_Wire:SolderWire-0.75sqmm_1x01_D1.25mm_OD3.5mm +Connector_Wire:SolderWire-0.75sqmm_1x01_D1.25mm_OD3.5mm_Relief +Connector_Wire:SolderWire-0.75sqmm_1x01_D1.25mm_OD3.5mm_Relief2x +Connector_Wire:SolderWire-0.75sqmm_1x02_P4.8mm_D1.25mm_OD2.3mm +Connector_Wire:SolderWire-0.75sqmm_1x02_P4.8mm_D1.25mm_OD2.3mm_Relief +Connector_Wire:SolderWire-0.75sqmm_1x02_P4.8mm_D1.25mm_OD2.3mm_Relief2x +Connector_Wire:SolderWire-0.75sqmm_1x02_P7mm_D1.25mm_OD3.5mm +Connector_Wire:SolderWire-0.75sqmm_1x02_P7mm_D1.25mm_OD3.5mm_Relief +Connector_Wire:SolderWire-0.75sqmm_1x02_P7mm_D1.25mm_OD3.5mm_Relief2x +Connector_Wire:SolderWire-0.75sqmm_1x03_P4.8mm_D1.25mm_OD2.3mm +Connector_Wire:SolderWire-0.75sqmm_1x03_P4.8mm_D1.25mm_OD2.3mm_Relief +Connector_Wire:SolderWire-0.75sqmm_1x03_P4.8mm_D1.25mm_OD2.3mm_Relief2x +Connector_Wire:SolderWire-0.75sqmm_1x03_P7mm_D1.25mm_OD3.5mm +Connector_Wire:SolderWire-0.75sqmm_1x03_P7mm_D1.25mm_OD3.5mm_Relief +Connector_Wire:SolderWire-0.75sqmm_1x03_P7mm_D1.25mm_OD3.5mm_Relief2x +Connector_Wire:SolderWire-0.75sqmm_1x04_P4.8mm_D1.25mm_OD2.3mm +Connector_Wire:SolderWire-0.75sqmm_1x04_P4.8mm_D1.25mm_OD2.3mm_Relief +Connector_Wire:SolderWire-0.75sqmm_1x04_P4.8mm_D1.25mm_OD2.3mm_Relief2x +Connector_Wire:SolderWire-0.75sqmm_1x04_P7mm_D1.25mm_OD3.5mm +Connector_Wire:SolderWire-0.75sqmm_1x04_P7mm_D1.25mm_OD3.5mm_Relief +Connector_Wire:SolderWire-0.75sqmm_1x04_P7mm_D1.25mm_OD3.5mm_Relief2x +Connector_Wire:SolderWire-0.75sqmm_1x05_P4.8mm_D1.25mm_OD2.3mm +Connector_Wire:SolderWire-0.75sqmm_1x05_P4.8mm_D1.25mm_OD2.3mm_Relief +Connector_Wire:SolderWire-0.75sqmm_1x05_P4.8mm_D1.25mm_OD2.3mm_Relief2x +Connector_Wire:SolderWire-0.75sqmm_1x05_P7mm_D1.25mm_OD3.5mm +Connector_Wire:SolderWire-0.75sqmm_1x05_P7mm_D1.25mm_OD3.5mm_Relief +Connector_Wire:SolderWire-0.75sqmm_1x05_P7mm_D1.25mm_OD3.5mm_Relief2x +Connector_Wire:SolderWire-0.75sqmm_1x06_P4.8mm_D1.25mm_OD2.3mm +Connector_Wire:SolderWire-0.75sqmm_1x06_P4.8mm_D1.25mm_OD2.3mm_Relief +Connector_Wire:SolderWire-0.75sqmm_1x06_P4.8mm_D1.25mm_OD2.3mm_Relief2x +Connector_Wire:SolderWire-0.75sqmm_1x06_P7mm_D1.25mm_OD3.5mm +Connector_Wire:SolderWire-0.75sqmm_1x06_P7mm_D1.25mm_OD3.5mm_Relief +Connector_Wire:SolderWire-0.75sqmm_1x06_P7mm_D1.25mm_OD3.5mm_Relief2x +Connector_Wire:SolderWire-1.5sqmm_1x01_D1.7mm_OD3.9mm +Connector_Wire:SolderWire-1.5sqmm_1x01_D1.7mm_OD3.9mm_Relief +Connector_Wire:SolderWire-1.5sqmm_1x01_D1.7mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-1.5sqmm_1x01_D1.7mm_OD3mm +Connector_Wire:SolderWire-1.5sqmm_1x01_D1.7mm_OD3mm_Relief +Connector_Wire:SolderWire-1.5sqmm_1x01_D1.7mm_OD3mm_Relief2x +Connector_Wire:SolderWire-1.5sqmm_1x02_P6mm_D1.7mm_OD3mm +Connector_Wire:SolderWire-1.5sqmm_1x02_P6mm_D1.7mm_OD3mm_Relief +Connector_Wire:SolderWire-1.5sqmm_1x02_P6mm_D1.7mm_OD3mm_Relief2x +Connector_Wire:SolderWire-1.5sqmm_1x02_P7.8mm_D1.7mm_OD3.9mm +Connector_Wire:SolderWire-1.5sqmm_1x02_P7.8mm_D1.7mm_OD3.9mm_Relief +Connector_Wire:SolderWire-1.5sqmm_1x02_P7.8mm_D1.7mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-1.5sqmm_1x03_P6mm_D1.7mm_OD3mm +Connector_Wire:SolderWire-1.5sqmm_1x03_P6mm_D1.7mm_OD3mm_Relief +Connector_Wire:SolderWire-1.5sqmm_1x03_P6mm_D1.7mm_OD3mm_Relief2x +Connector_Wire:SolderWire-1.5sqmm_1x03_P7.8mm_D1.7mm_OD3.9mm +Connector_Wire:SolderWire-1.5sqmm_1x03_P7.8mm_D1.7mm_OD3.9mm_Relief +Connector_Wire:SolderWire-1.5sqmm_1x03_P7.8mm_D1.7mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-1.5sqmm_1x04_P6mm_D1.7mm_OD3mm +Connector_Wire:SolderWire-1.5sqmm_1x04_P6mm_D1.7mm_OD3mm_Relief +Connector_Wire:SolderWire-1.5sqmm_1x04_P6mm_D1.7mm_OD3mm_Relief2x +Connector_Wire:SolderWire-1.5sqmm_1x04_P7.8mm_D1.7mm_OD3.9mm +Connector_Wire:SolderWire-1.5sqmm_1x04_P7.8mm_D1.7mm_OD3.9mm_Relief +Connector_Wire:SolderWire-1.5sqmm_1x04_P7.8mm_D1.7mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-1.5sqmm_1x05_P6mm_D1.7mm_OD3mm +Connector_Wire:SolderWire-1.5sqmm_1x05_P6mm_D1.7mm_OD3mm_Relief +Connector_Wire:SolderWire-1.5sqmm_1x05_P6mm_D1.7mm_OD3mm_Relief2x +Connector_Wire:SolderWire-1.5sqmm_1x05_P7.8mm_D1.7mm_OD3.9mm +Connector_Wire:SolderWire-1.5sqmm_1x05_P7.8mm_D1.7mm_OD3.9mm_Relief +Connector_Wire:SolderWire-1.5sqmm_1x05_P7.8mm_D1.7mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-1.5sqmm_1x06_P6mm_D1.7mm_OD3mm +Connector_Wire:SolderWire-1.5sqmm_1x06_P6mm_D1.7mm_OD3mm_Relief +Connector_Wire:SolderWire-1.5sqmm_1x06_P6mm_D1.7mm_OD3mm_Relief2x +Connector_Wire:SolderWire-1.5sqmm_1x06_P7.8mm_D1.7mm_OD3.9mm +Connector_Wire:SolderWire-1.5sqmm_1x06_P7.8mm_D1.7mm_OD3.9mm_Relief +Connector_Wire:SolderWire-1.5sqmm_1x06_P7.8mm_D1.7mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-1sqmm_1x01_D1.4mm_OD2.7mm +Connector_Wire:SolderWire-1sqmm_1x01_D1.4mm_OD2.7mm_Relief +Connector_Wire:SolderWire-1sqmm_1x01_D1.4mm_OD2.7mm_Relief2x +Connector_Wire:SolderWire-1sqmm_1x01_D1.4mm_OD3.9mm +Connector_Wire:SolderWire-1sqmm_1x01_D1.4mm_OD3.9mm_Relief +Connector_Wire:SolderWire-1sqmm_1x01_D1.4mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-1sqmm_1x02_P5.4mm_D1.4mm_OD2.7mm +Connector_Wire:SolderWire-1sqmm_1x02_P5.4mm_D1.4mm_OD2.7mm_Relief +Connector_Wire:SolderWire-1sqmm_1x02_P5.4mm_D1.4mm_OD2.7mm_Relief2x +Connector_Wire:SolderWire-1sqmm_1x02_P7.8mm_D1.4mm_OD3.9mm +Connector_Wire:SolderWire-1sqmm_1x02_P7.8mm_D1.4mm_OD3.9mm_Relief +Connector_Wire:SolderWire-1sqmm_1x02_P7.8mm_D1.4mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-1sqmm_1x03_P5.4mm_D1.4mm_OD2.7mm +Connector_Wire:SolderWire-1sqmm_1x03_P5.4mm_D1.4mm_OD2.7mm_Relief +Connector_Wire:SolderWire-1sqmm_1x03_P5.4mm_D1.4mm_OD2.7mm_Relief2x +Connector_Wire:SolderWire-1sqmm_1x03_P7.8mm_D1.4mm_OD3.9mm +Connector_Wire:SolderWire-1sqmm_1x03_P7.8mm_D1.4mm_OD3.9mm_Relief +Connector_Wire:SolderWire-1sqmm_1x03_P7.8mm_D1.4mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-1sqmm_1x04_P5.4mm_D1.4mm_OD2.7mm +Connector_Wire:SolderWire-1sqmm_1x04_P5.4mm_D1.4mm_OD2.7mm_Relief +Connector_Wire:SolderWire-1sqmm_1x04_P5.4mm_D1.4mm_OD2.7mm_Relief2x +Connector_Wire:SolderWire-1sqmm_1x04_P7.8mm_D1.4mm_OD3.9mm +Connector_Wire:SolderWire-1sqmm_1x04_P7.8mm_D1.4mm_OD3.9mm_Relief +Connector_Wire:SolderWire-1sqmm_1x04_P7.8mm_D1.4mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-1sqmm_1x05_P5.4mm_D1.4mm_OD2.7mm +Connector_Wire:SolderWire-1sqmm_1x05_P5.4mm_D1.4mm_OD2.7mm_Relief +Connector_Wire:SolderWire-1sqmm_1x05_P5.4mm_D1.4mm_OD2.7mm_Relief2x +Connector_Wire:SolderWire-1sqmm_1x05_P7.8mm_D1.4mm_OD3.9mm +Connector_Wire:SolderWire-1sqmm_1x05_P7.8mm_D1.4mm_OD3.9mm_Relief +Connector_Wire:SolderWire-1sqmm_1x05_P7.8mm_D1.4mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-1sqmm_1x06_P5.4mm_D1.4mm_OD2.7mm +Connector_Wire:SolderWire-1sqmm_1x06_P5.4mm_D1.4mm_OD2.7mm_Relief +Connector_Wire:SolderWire-1sqmm_1x06_P5.4mm_D1.4mm_OD2.7mm_Relief2x +Connector_Wire:SolderWire-1sqmm_1x06_P7.8mm_D1.4mm_OD3.9mm +Connector_Wire:SolderWire-1sqmm_1x06_P7.8mm_D1.4mm_OD3.9mm_Relief +Connector_Wire:SolderWire-1sqmm_1x06_P7.8mm_D1.4mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-2.5sqmm_1x01_D2.4mm_OD3.6mm +Connector_Wire:SolderWire-2.5sqmm_1x01_D2.4mm_OD3.6mm_Relief +Connector_Wire:SolderWire-2.5sqmm_1x01_D2.4mm_OD3.6mm_Relief2x +Connector_Wire:SolderWire-2.5sqmm_1x01_D2.4mm_OD4.4mm +Connector_Wire:SolderWire-2.5sqmm_1x01_D2.4mm_OD4.4mm_Relief +Connector_Wire:SolderWire-2.5sqmm_1x01_D2.4mm_OD4.4mm_Relief2x +Connector_Wire:SolderWire-2.5sqmm_1x02_P7.2mm_D2.4mm_OD3.6mm +Connector_Wire:SolderWire-2.5sqmm_1x02_P7.2mm_D2.4mm_OD3.6mm_Relief +Connector_Wire:SolderWire-2.5sqmm_1x02_P7.2mm_D2.4mm_OD3.6mm_Relief2x +Connector_Wire:SolderWire-2.5sqmm_1x02_P8.8mm_D2.4mm_OD4.4mm +Connector_Wire:SolderWire-2.5sqmm_1x02_P8.8mm_D2.4mm_OD4.4mm_Relief +Connector_Wire:SolderWire-2.5sqmm_1x02_P8.8mm_D2.4mm_OD4.4mm_Relief2x +Connector_Wire:SolderWire-2.5sqmm_1x03_P7.2mm_D2.4mm_OD3.6mm +Connector_Wire:SolderWire-2.5sqmm_1x03_P7.2mm_D2.4mm_OD3.6mm_Relief +Connector_Wire:SolderWire-2.5sqmm_1x03_P7.2mm_D2.4mm_OD3.6mm_Relief2x +Connector_Wire:SolderWire-2.5sqmm_1x03_P8.8mm_D2.4mm_OD4.4mm +Connector_Wire:SolderWire-2.5sqmm_1x03_P8.8mm_D2.4mm_OD4.4mm_Relief +Connector_Wire:SolderWire-2.5sqmm_1x03_P8.8mm_D2.4mm_OD4.4mm_Relief2x +Connector_Wire:SolderWire-2.5sqmm_1x04_P7.2mm_D2.4mm_OD3.6mm +Connector_Wire:SolderWire-2.5sqmm_1x04_P7.2mm_D2.4mm_OD3.6mm_Relief +Connector_Wire:SolderWire-2.5sqmm_1x04_P7.2mm_D2.4mm_OD3.6mm_Relief2x +Connector_Wire:SolderWire-2.5sqmm_1x04_P8.8mm_D2.4mm_OD4.4mm +Connector_Wire:SolderWire-2.5sqmm_1x04_P8.8mm_D2.4mm_OD4.4mm_Relief +Connector_Wire:SolderWire-2.5sqmm_1x04_P8.8mm_D2.4mm_OD4.4mm_Relief2x +Connector_Wire:SolderWire-2.5sqmm_1x05_P7.2mm_D2.4mm_OD3.6mm +Connector_Wire:SolderWire-2.5sqmm_1x05_P7.2mm_D2.4mm_OD3.6mm_Relief +Connector_Wire:SolderWire-2.5sqmm_1x05_P7.2mm_D2.4mm_OD3.6mm_Relief2x +Connector_Wire:SolderWire-2.5sqmm_1x05_P8.8mm_D2.4mm_OD4.4mm +Connector_Wire:SolderWire-2.5sqmm_1x05_P8.8mm_D2.4mm_OD4.4mm_Relief +Connector_Wire:SolderWire-2.5sqmm_1x05_P8.8mm_D2.4mm_OD4.4mm_Relief2x +Connector_Wire:SolderWire-2.5sqmm_1x06_P7.2mm_D2.4mm_OD3.6mm +Connector_Wire:SolderWire-2.5sqmm_1x06_P7.2mm_D2.4mm_OD3.6mm_Relief +Connector_Wire:SolderWire-2.5sqmm_1x06_P7.2mm_D2.4mm_OD3.6mm_Relief2x +Connector_Wire:SolderWire-2.5sqmm_1x06_P8.8mm_D2.4mm_OD4.4mm +Connector_Wire:SolderWire-2.5sqmm_1x06_P8.8mm_D2.4mm_OD4.4mm_Relief +Connector_Wire:SolderWire-2.5sqmm_1x06_P8.8mm_D2.4mm_OD4.4mm_Relief2x +Connector_Wire:SolderWire-2sqmm_1x01_D2mm_OD3.9mm +Connector_Wire:SolderWire-2sqmm_1x01_D2mm_OD3.9mm_Relief +Connector_Wire:SolderWire-2sqmm_1x01_D2mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-2sqmm_1x02_P7.8mm_D2mm_OD3.9mm +Connector_Wire:SolderWire-2sqmm_1x02_P7.8mm_D2mm_OD3.9mm_Relief +Connector_Wire:SolderWire-2sqmm_1x02_P7.8mm_D2mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-2sqmm_1x03_P7.8mm_D2mm_OD3.9mm +Connector_Wire:SolderWire-2sqmm_1x03_P7.8mm_D2mm_OD3.9mm_Relief +Connector_Wire:SolderWire-2sqmm_1x03_P7.8mm_D2mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-2sqmm_1x04_P7.8mm_D2mm_OD3.9mm +Connector_Wire:SolderWire-2sqmm_1x04_P7.8mm_D2mm_OD3.9mm_Relief +Connector_Wire:SolderWire-2sqmm_1x04_P7.8mm_D2mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-2sqmm_1x05_P7.8mm_D2mm_OD3.9mm +Connector_Wire:SolderWire-2sqmm_1x05_P7.8mm_D2mm_OD3.9mm_Relief +Connector_Wire:SolderWire-2sqmm_1x05_P7.8mm_D2mm_OD3.9mm_Relief2x +Connector_Wire:SolderWire-2sqmm_1x06_P7.8mm_D2mm_OD3.9mm +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 +Connector_Wuerth:Wuerth_WR-WTB_64800511622_1x05_P1.50mm_Vertical +Connector_Wuerth:Wuerth_WR-WTB_64800611622_1x06_P1.50mm_Vertical +Connector_Wuerth:Wuerth_WR-WTB_64800711622_1x07_P1.50mm_Vertical +Connector_Wuerth:Wuerth_WR-WTB_64800811622_1x08_P1.50mm_Vertical +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_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 +Converter_ACDC:Converter_ACDC_MeanWell_IRM-03-xx_THT +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 +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 +Converter_DCDC:Converter_DCDC_Bothhand_CFUSxxxx_THT +Converter_DCDC:Converter_DCDC_Cincon_EC5BExx_Dual_THT +Converter_DCDC:Converter_DCDC_Cincon_EC5BExx_Single_THT +Converter_DCDC:Converter_DCDC_Cincon_EC6Cxx_Dual-Triple_THT +Converter_DCDC:Converter_DCDC_Cincon_EC6Cxx_Single_THT +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 +Converter_DCDC:Converter_DCDC_Murata_MEE1SxxxxSC_THT +Converter_DCDC:Converter_DCDC_Murata_MEE3SxxxxSC_THT +Converter_DCDC:Converter_DCDC_muRata_MEJ1DxxxxSC_THT +Converter_DCDC:Converter_DCDC_muRata_MEJ1SxxxxSC_THT +Converter_DCDC:Converter_DCDC_Murata_MGJ2DxxxxxxSC_THT +Converter_DCDC:Converter_DCDC_Murata_MGJ3 +Converter_DCDC:Converter_DCDC_Murata_MYRxP +Converter_DCDC:Converter_DCDC_Murata_NCS1SxxxxSC_THT +Converter_DCDC:Converter_DCDC_Murata_NMAxxxxDC_THT +Converter_DCDC:Converter_DCDC_Murata_NMAxxxxSC_THT +Converter_DCDC:Converter_DCDC_Murata_NXExSxxxxMC_SMD +Converter_DCDC:Converter_DCDC_Murata_OKI-78SR_Horizontal +Converter_DCDC:Converter_DCDC_Murata_OKI-78SR_Vertical +Converter_DCDC:Converter_DCDC_RECOM_R-78B-2.0_THT +Converter_DCDC:Converter_DCDC_RECOM_R-78E-0.5_THT +Converter_DCDC:Converter_DCDC_RECOM_R-78HB-0.5L_THT +Converter_DCDC:Converter_DCDC_RECOM_R-78HB-0.5_THT +Converter_DCDC:Converter_DCDC_RECOM_R-78S-0.1_THT +Converter_DCDC:Converter_DCDC_RECOM_R5xxxDA_THT +Converter_DCDC:Converter_DCDC_RECOM_R5xxxPA_THT +Converter_DCDC:Converter_DCDC_RECOM_RCD-24_THT +Converter_DCDC:Converter_DCDC_RECOM_RPA60-xxxxSFW +Converter_DCDC:Converter_DCDC_RECOM_RPMx.x-x.0 +Converter_DCDC:Converter_DCDC_Silvertel_Ag54xx +Converter_DCDC:Converter_DCDC_Silvertel_Ag5810 +Converter_DCDC:Converter_DCDC_Silvertel_Ag99xxLP_THT +Converter_DCDC:Converter_DCDC_TRACO_TBA1-xxxxE_Dual_THT +Converter_DCDC:Converter_DCDC_TRACO_TBA1-xxxxE_Single_THT +Converter_DCDC:Converter_DCDC_TRACO_TBA2-xxxx_Dual_THT +Converter_DCDC:Converter_DCDC_TRACO_TBA2-xxxx_Single_THT +Converter_DCDC:Converter_DCDC_TRACO_TDN_5-xxxxWISM_SMD +Converter_DCDC:Converter_DCDC_TRACO_TDN_5-xxxxWI_THT +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 +Converter_DCDC:Converter_DCDC_TRACO_TMA-15xxS_24xxS_Single_THT +Converter_DCDC:Converter_DCDC_TRACO_TME_03xxS_05xxS_12xxS_Single_THT +Converter_DCDC:Converter_DCDC_TRACO_TME_24xxS_Single_THT +Converter_DCDC:Converter_DCDC_TRACO_TMR-1-xxxx_Dual_THT +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 +Converter_DCDC:Converter_DCDC_XP_POWER-IAxxxxS_THT +Converter_DCDC:Converter_DCDC_XP_POWER-IHxxxxDH_THT +Converter_DCDC:Converter_DCDC_XP_POWER-IHxxxxD_THT +Converter_DCDC:Converter_DCDC_XP_POWER-IHxxxxSH_THT +Converter_DCDC:Converter_DCDC_XP_POWER-IHxxxxS_THT +Converter_DCDC:Converter_DCDC_XP_POWER-ISU02_SMD +Converter_DCDC:Converter_DCDC_XP_POWER-ITQxxxxS-H_THT +Converter_DCDC:Converter_DCDC_XP_POWER-ITXxxxxSA_THT +Converter_DCDC:Converter_DCDC_XP_POWER-ITxxxxxS_THT +Converter_DCDC:Converter_DCDC_XP_POWER_JTDxxxxxxx_THT +Converter_DCDC:Converter_DCDC_XP_POWER_JTExxxxDxx_THT +Crystal:Crystal_AT310_D3.0mm_L10.0mm_Horizontal +Crystal:Crystal_AT310_D3.0mm_L10.0mm_Horizontal_1EP_style1 +Crystal:Crystal_AT310_D3.0mm_L10.0mm_Horizontal_1EP_style2 +Crystal:Crystal_AT310_D3.0mm_L10.0mm_Vertical +Crystal:Crystal_C26-LF_D2.1mm_L6.5mm_Horizontal +Crystal:Crystal_C26-LF_D2.1mm_L6.5mm_Horizontal_1EP_style1 +Crystal:Crystal_C26-LF_D2.1mm_L6.5mm_Horizontal_1EP_style2 +Crystal:Crystal_C26-LF_D2.1mm_L6.5mm_Vertical +Crystal:Crystal_C38-LF_D3.0mm_L8.0mm_Horizontal +Crystal:Crystal_C38-LF_D3.0mm_L8.0mm_Horizontal_1EP_style1 +Crystal:Crystal_C38-LF_D3.0mm_L8.0mm_Horizontal_1EP_style2 +Crystal:Crystal_C38-LF_D3.0mm_L8.0mm_Vertical +Crystal:Crystal_DS10_D1.0mm_L4.3mm_Horizontal +Crystal:Crystal_DS10_D1.0mm_L4.3mm_Horizontal_1EP_style1 +Crystal:Crystal_DS10_D1.0mm_L4.3mm_Horizontal_1EP_style2 +Crystal:Crystal_DS10_D1.0mm_L4.3mm_Vertical +Crystal:Crystal_DS15_D1.5mm_L5.0mm_Horizontal +Crystal:Crystal_DS15_D1.5mm_L5.0mm_Horizontal_1EP_style1 +Crystal:Crystal_DS15_D1.5mm_L5.0mm_Horizontal_1EP_style2 +Crystal:Crystal_DS15_D1.5mm_L5.0mm_Vertical +Crystal:Crystal_DS26_D2.0mm_L6.0mm_Horizontal +Crystal:Crystal_DS26_D2.0mm_L6.0mm_Horizontal_1EP_style1 +Crystal:Crystal_DS26_D2.0mm_L6.0mm_Horizontal_1EP_style2 +Crystal:Crystal_DS26_D2.0mm_L6.0mm_Vertical +Crystal:Crystal_HC18-U_Horizontal +Crystal:Crystal_HC18-U_Horizontal_1EP_style1 +Crystal:Crystal_HC18-U_Horizontal_1EP_style2 +Crystal:Crystal_HC18-U_Vertical +Crystal:Crystal_HC33-U_Horizontal +Crystal:Crystal_HC33-U_Horizontal_1EP_style1 +Crystal:Crystal_HC33-U_Horizontal_1EP_style2 +Crystal:Crystal_HC33-U_Vertical +Crystal:Crystal_HC35-U +Crystal:Crystal_HC49-4H_Vertical +Crystal:Crystal_HC49-U-3Pin_Vertical +Crystal:Crystal_HC49-U_Horizontal +Crystal:Crystal_HC49-U_Horizontal_1EP_style1 +Crystal:Crystal_HC49-U_Horizontal_1EP_style2 +Crystal:Crystal_HC49-U_Vertical +Crystal:Crystal_HC50_Horizontal +Crystal:Crystal_HC50_Horizontal_1EP_style1 +Crystal:Crystal_HC50_Horizontal_1EP_style2 +Crystal:Crystal_HC50_Vertical +Crystal:Crystal_HC51-U_Vertical +Crystal:Crystal_HC51_Horizontal +Crystal:Crystal_HC51_Horizontal_1EP_style1 +Crystal:Crystal_HC51_Horizontal_1EP_style2 +Crystal:Crystal_HC52-6mm_Horizontal +Crystal:Crystal_HC52-6mm_Horizontal_1EP_style1 +Crystal:Crystal_HC52-6mm_Horizontal_1EP_style2 +Crystal:Crystal_HC52-6mm_Vertical +Crystal:Crystal_HC52-8mm_Horizontal +Crystal:Crystal_HC52-8mm_Horizontal_1EP_style1 +Crystal:Crystal_HC52-8mm_Horizontal_1EP_style2 +Crystal:Crystal_HC52-8mm_Vertical +Crystal:Crystal_HC52-U-3Pin_Vertical +Crystal:Crystal_HC52-U_Horizontal +Crystal:Crystal_HC52-U_Horizontal_1EP_style1 +Crystal:Crystal_HC52-U_Horizontal_1EP_style2 +Crystal:Crystal_HC52-U_Vertical +Crystal:Crystal_Round_D1.0mm_Vertical +Crystal:Crystal_Round_D1.5mm_Vertical +Crystal:Crystal_Round_D2.0mm_Vertical +Crystal:Crystal_Round_D3.0mm_Vertical +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 +Crystal:Crystal_SMD_2520-4Pin_2.5x2.0mm +Crystal:Crystal_SMD_3215-2Pin_3.2x1.5mm +Crystal:Crystal_SMD_3225-4Pin_3.2x2.5mm +Crystal:Crystal_SMD_3225-4Pin_3.2x2.5mm_HandSoldering +Crystal:Crystal_SMD_5032-2Pin_5.0x3.2mm +Crystal:Crystal_SMD_5032-2Pin_5.0x3.2mm_HandSoldering +Crystal:Crystal_SMD_5032-4Pin_5.0x3.2mm +Crystal:Crystal_SMD_7050-2Pin_7.0x5.0mm +Crystal:Crystal_SMD_7050-2Pin_7.0x5.0mm_HandSoldering +Crystal:Crystal_SMD_7050-4Pin_7.0x5.0mm +Crystal:Crystal_SMD_Abracon_ABM10-4Pin_2.5x2.0mm +Crystal:Crystal_SMD_Abracon_ABM3-2Pin_5.0x3.2mm +Crystal:Crystal_SMD_Abracon_ABM3-2Pin_5.0x3.2mm_HandSoldering +Crystal:Crystal_SMD_Abracon_ABM3B-4Pin_5.0x3.2mm +Crystal:Crystal_SMD_Abracon_ABM3C-4Pin_5.0x3.2mm +Crystal:Crystal_SMD_Abracon_ABM7-2Pin_6.0x3.5mm +Crystal:Crystal_SMD_Abracon_ABM8AIG-4Pin_3.2x2.5mm +Crystal:Crystal_SMD_Abracon_ABM8G-4Pin_3.2x2.5mm +Crystal:Crystal_SMD_Abracon_ABS25-4Pin_8.0x3.8mm +Crystal:Crystal_SMD_ECS_CSM3X-2Pin_7.6x4.1mm +Crystal:Crystal_SMD_EuroQuartz_EQ161-2Pin_3.2x1.5mm +Crystal:Crystal_SMD_EuroQuartz_EQ161-2Pin_3.2x1.5mm_HandSoldering +Crystal:Crystal_SMD_EuroQuartz_MJ-4Pin_5.0x3.2mm +Crystal:Crystal_SMD_EuroQuartz_MJ-4Pin_5.0x3.2mm_HandSoldering +Crystal:Crystal_SMD_EuroQuartz_MQ-4Pin_7.0x5.0mm +Crystal:Crystal_SMD_EuroQuartz_MQ-4Pin_7.0x5.0mm_HandSoldering +Crystal:Crystal_SMD_EuroQuartz_MQ2-2Pin_7.0x5.0mm +Crystal:Crystal_SMD_EuroQuartz_MQ2-2Pin_7.0x5.0mm_HandSoldering +Crystal:Crystal_SMD_EuroQuartz_MT-4Pin_3.2x2.5mm +Crystal:Crystal_SMD_EuroQuartz_MT-4Pin_3.2x2.5mm_HandSoldering +Crystal:Crystal_SMD_EuroQuartz_X22-4Pin_2.5x2.0mm +Crystal:Crystal_SMD_EuroQuartz_X22-4Pin_2.5x2.0mm_HandSoldering +Crystal:Crystal_SMD_FOX_FE-2Pin_7.5x5.0mm +Crystal:Crystal_SMD_FOX_FE-2Pin_7.5x5.0mm_HandSoldering +Crystal:Crystal_SMD_FOX_FQ7050-2Pin_7.0x5.0mm +Crystal:Crystal_SMD_FOX_FQ7050-2Pin_7.0x5.0mm_HandSoldering +Crystal:Crystal_SMD_FOX_FQ7050-4Pin_7.0x5.0mm +Crystal:Crystal_SMD_FrontierElectronics_FM206 +Crystal:Crystal_SMD_G8-2Pin_3.2x1.5mm +Crystal:Crystal_SMD_G8-2Pin_3.2x1.5mm_HandSoldering +Crystal:Crystal_SMD_HC49-SD +Crystal:Crystal_SMD_HC49-SD_HandSoldering +Crystal:Crystal_SMD_MicroCrystal_CC1V-T1A-2Pin_8.0x3.7mm +Crystal:Crystal_SMD_MicroCrystal_CC1V-T1A-2Pin_8.0x3.7mm_HandSoldering +Crystal:Crystal_SMD_MicroCrystal_CC4V-T1A-2Pin_5.0x1.9mm +Crystal:Crystal_SMD_MicroCrystal_CC4V-T1A-2Pin_5.0x1.9mm_HandSoldering +Crystal:Crystal_SMD_MicroCrystal_CC5V-T1A-2Pin_4.1x1.5mm +Crystal:Crystal_SMD_MicroCrystal_CC5V-T1A-2Pin_4.1x1.5mm_HandSoldering +Crystal:Crystal_SMD_MicroCrystal_CC7V-T1A-2Pin_3.2x1.5mm +Crystal:Crystal_SMD_MicroCrystal_CC7V-T1A-2Pin_3.2x1.5mm_HandSoldering +Crystal:Crystal_SMD_MicroCrystal_CC8V-T1A-2Pin_2.0x1.2mm +Crystal:Crystal_SMD_MicroCrystal_CC8V-T1A-2Pin_2.0x1.2mm_HandSoldering +Crystal:Crystal_SMD_MicroCrystal_CM9V-T1A-2Pin_1.6x1.0mm +Crystal:Crystal_SMD_MicroCrystal_CM9V-T1A-2Pin_1.6x1.0mm_HandSoldering +Crystal:Crystal_SMD_MicroCrystal_MS1V-T1K +Crystal:Crystal_SMD_MicroCrystal_MS3V-T1R +Crystal:Crystal_SMD_Qantek_QC5CB-2Pin_5x3.2mm +Crystal:Crystal_SMD_SeikoEpson_FA128-4Pin_2.0x1.6mm +Crystal:Crystal_SMD_SeikoEpson_FA238-4Pin_3.2x2.5mm +Crystal:Crystal_SMD_SeikoEpson_FA238-4Pin_3.2x2.5mm_HandSoldering +Crystal:Crystal_SMD_SeikoEpson_FA238V-4Pin_3.2x2.5mm +Crystal:Crystal_SMD_SeikoEpson_FA238V-4Pin_3.2x2.5mm_HandSoldering +Crystal:Crystal_SMD_SeikoEpson_MA406-4Pin_11.7x4.0mm +Crystal:Crystal_SMD_SeikoEpson_MA406-4Pin_11.7x4.0mm_HandSoldering +Crystal:Crystal_SMD_SeikoEpson_MA505-2Pin_12.7x5.1mm +Crystal:Crystal_SMD_SeikoEpson_MA505-2Pin_12.7x5.1mm_HandSoldering +Crystal:Crystal_SMD_SeikoEpson_MA506-4Pin_12.7x5.1mm +Crystal:Crystal_SMD_SeikoEpson_MA506-4Pin_12.7x5.1mm_HandSoldering +Crystal:Crystal_SMD_SeikoEpson_MC146-4Pin_6.7x1.5mm +Crystal:Crystal_SMD_SeikoEpson_MC146-4Pin_6.7x1.5mm_HandSoldering +Crystal:Crystal_SMD_SeikoEpson_MC156-4Pin_7.1x2.5mm +Crystal:Crystal_SMD_SeikoEpson_MC156-4Pin_7.1x2.5mm_HandSoldering +Crystal:Crystal_SMD_SeikoEpson_MC306-4Pin_8.0x3.2mm +Crystal:Crystal_SMD_SeikoEpson_MC306-4Pin_8.0x3.2mm_HandSoldering +Crystal:Crystal_SMD_SeikoEpson_MC405-2Pin_9.6x4.1mm +Crystal:Crystal_SMD_SeikoEpson_MC405-2Pin_9.6x4.1mm_HandSoldering +Crystal:Crystal_SMD_SeikoEpson_MC406-4Pin_9.6x4.1mm +Crystal:Crystal_SMD_SeikoEpson_MC406-4Pin_9.6x4.1mm_HandSoldering +Crystal:Crystal_SMD_SeikoEpson_TSX3225-4Pin_3.2x2.5mm +Crystal:Crystal_SMD_SeikoEpson_TSX3225-4Pin_3.2x2.5mm_HandSoldering +Crystal:Crystal_SMD_TXC_7A-2Pin_5x3.2mm +Crystal:Crystal_SMD_TXC_7M-4Pin_3.2x2.5mm +Crystal:Crystal_SMD_TXC_7M-4Pin_3.2x2.5mm_HandSoldering +Crystal:Crystal_SMD_TXC_9HT11-2Pin_2.0x1.2mm +Crystal:Crystal_SMD_TXC_9HT11-2Pin_2.0x1.2mm_HandSoldering +Crystal:Crystal_SMD_TXC_AX_8045-2Pin_8.0x4.5mm +Crystal:Resonator-2Pin_W10.0mm_H5.0mm +Crystal:Resonator-2Pin_W6.0mm_H3.0mm +Crystal:Resonator-2Pin_W7.0mm_H2.5mm +Crystal:Resonator-2Pin_W8.0mm_H3.5mm +Crystal:Resonator-3Pin_W10.0mm_H5.0mm +Crystal:Resonator-3Pin_W6.0mm_H3.0mm +Crystal:Resonator-3Pin_W7.0mm_H2.5mm +Crystal:Resonator-3Pin_W8.0mm_H3.5mm +Crystal:Resonator_Murata_CSTLSxxxG-3Pin_W8.0mm_H3.0mm +Crystal:Resonator_Murata_CSTLSxxxX-3Pin_W5.5mm_H3.0mm +Crystal:Resonator_Murata_DSN6-3Pin_W7.0mm_H2.5mm +Crystal:Resonator_Murata_DSS6-3Pin_W7.0mm_H2.5mm +Crystal:Resonator_SMD-3Pin_7.2x3.0mm +Crystal:Resonator_SMD-3Pin_7.2x3.0mm_HandSoldering +Crystal:Resonator_SMD_Murata_CDSCB-2Pin_4.5x2.0mm +Crystal:Resonator_SMD_Murata_CDSCB-2Pin_4.5x2.0mm_HandSoldering +Crystal:Resonator_SMD_Murata_CSTCR_4.5x2x1.15mm +Crystal:Resonator_SMD_Murata_CSTxExxV-3Pin_3.0x1.1mm +Crystal:Resonator_SMD_Murata_CSTxExxV-3Pin_3.0x1.1mm_HandSoldering +Crystal:Resonator_SMD_Murata_SFECV-3Pin_6.9x2.9mm +Crystal:Resonator_SMD_Murata_SFECV-3Pin_6.9x2.9mm_HandSoldering +Crystal:Resonator_SMD_Murata_SFSKA-3Pin_7.9x3.8mm +Crystal:Resonator_SMD_Murata_SFSKA-3Pin_7.9x3.8mm_HandSoldering +Crystal:Resonator_SMD_Murata_TPSKA-3Pin_7.9x3.8mm +Crystal:Resonator_SMD_Murata_TPSKA-3Pin_7.9x3.8mm_HandSoldering +Diode_SMD:Diode_Bridge_Bourns_CD-DF4xxS +Diode_SMD:Diode_Bridge_Diotec_ABS +Diode_SMD:Diode_Bridge_Diotec_MicroDil_3.0x3.0x1.8mm +Diode_SMD:Diode_Bridge_Diotec_SO-DIL-Slim +Diode_SMD:Diode_Bridge_OnSemi_SDIP-4L +Diode_SMD:Diode_Bridge_Vishay_DFS +Diode_SMD:Diode_Bridge_Vishay_DFSFlat +Diode_SMD:Diode_Bridge_Vishay_MBLS +Diode_SMD:D_01005_0402Metric +Diode_SMD:D_01005_0402Metric_Pad0.57x0.30mm_HandSolder +Diode_SMD:D_0201_0603Metric +Diode_SMD:D_0201_0603Metric_Pad0.64x0.40mm_HandSolder +Diode_SMD:D_0402_1005Metric +Diode_SMD:D_0402_1005Metric_Pad0.77x0.64mm_HandSolder +Diode_SMD:D_0603_1608Metric +Diode_SMD:D_0603_1608Metric_Pad1.05x0.95mm_HandSolder +Diode_SMD:D_0805_2012Metric +Diode_SMD:D_0805_2012Metric_Pad1.15x1.40mm_HandSolder +Diode_SMD:D_1206_3216Metric +Diode_SMD:D_1206_3216Metric_Pad1.42x1.75mm_HandSolder +Diode_SMD:D_1210_3225Metric +Diode_SMD:D_1210_3225Metric_Pad1.42x2.65mm_HandSolder +Diode_SMD:D_1812_4532Metric +Diode_SMD:D_1812_4532Metric_Pad1.30x3.40mm_HandSolder +Diode_SMD:D_2010_5025Metric +Diode_SMD:D_2010_5025Metric_Pad1.52x2.65mm_HandSolder +Diode_SMD:D_2114_3652Metric +Diode_SMD:D_2114_3652Metric_Pad1.85x3.75mm_HandSolder +Diode_SMD:D_2512_6332Metric +Diode_SMD:D_2512_6332Metric_Pad1.52x3.35mm_HandSolder +Diode_SMD:D_3220_8050Metric +Diode_SMD:D_3220_8050Metric_Pad2.65x5.15mm_HandSolder +Diode_SMD:D_MELF-RM10_Universal_Handsoldering +Diode_SMD:D_MELF +Diode_SMD:D_MELF_Handsoldering +Diode_SMD:D_MicroMELF +Diode_SMD:D_MicroMELF_Handsoldering +Diode_SMD:D_MicroSMP_AK +Diode_SMD:D_MicroSMP_KA +Diode_SMD:D_MiniMELF +Diode_SMD:D_MiniMELF_Handsoldering +Diode_SMD:D_PowerDI-123 +Diode_SMD:D_PowerDI-5 +Diode_SMD:D_Powermite2_AK +Diode_SMD:D_Powermite2_KA +Diode_SMD:D_Powermite3 +Diode_SMD:D_Powermite_AK +Diode_SMD:D_Powermite_KA +Diode_SMD:D_QFN_3.3x3.3mm_P0.65mm +Diode_SMD:D_SC-80 +Diode_SMD:D_SC-80_HandSoldering +Diode_SMD:D_SMA-SMB_Universal_Handsoldering +Diode_SMD:D_SMA +Diode_SMD:D_SMA_Handsoldering +Diode_SMD:D_SMB-SMC_Universal_Handsoldering +Diode_SMD:D_SMB +Diode_SMD:D_SMB_Handsoldering +Diode_SMD:D_SMB_Modified +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 +Diode_SMD:D_SOD-128 +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 +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 +Diode_THT:Diode_Bridge_15.7x15.7x6.3mm_P10.8mm +Diode_THT:Diode_Bridge_16.7x16.7x6.3mm_P10.8mm +Diode_THT:Diode_Bridge_19.0x19.0x6.8mm_P12.7mm +Diode_THT:Diode_Bridge_19.0x3.5x10.0mm_P5.0mm +Diode_THT:Diode_Bridge_28.6x28.6x7.3mm_P18.0mm_P11.6mm +Diode_THT:Diode_Bridge_32.0x5.6x17.0mm_P10.0mm_P7.5mm +Diode_THT:Diode_Bridge_Comchip_SCVB-L +Diode_THT:Diode_Bridge_DIGITRON_KBPC_T +Diode_THT:Diode_Bridge_DIP-4_W5.08mm_P2.54mm +Diode_THT:Diode_Bridge_DIP-4_W7.62mm_P5.08mm +Diode_THT:Diode_Bridge_GeneSiC_KBPC_T +Diode_THT:Diode_Bridge_GeneSiC_KBPC_W +Diode_THT:Diode_Bridge_IXYS_GUFP +Diode_THT:Diode_Bridge_Round_D8.9mm +Diode_THT:Diode_Bridge_Round_D9.0mm +Diode_THT:Diode_Bridge_Round_D9.8mm +Diode_THT:Diode_Bridge_Vishay_GBL +Diode_THT:Diode_Bridge_Vishay_GBU +Diode_THT:Diode_Bridge_Vishay_KBL +Diode_THT:Diode_Bridge_Vishay_KBPC1 +Diode_THT:Diode_Bridge_Vishay_KBPC6 +Diode_THT:Diode_Bridge_Vishay_KBPM +Diode_THT:Diode_Bridge_Vishay_KBU +Diode_THT:D_5KPW_P12.70mm_Horizontal +Diode_THT:D_5KPW_P7.62mm_Vertical_AnodeUp +Diode_THT:D_5KPW_P7.62mm_Vertical_KathodeUp +Diode_THT:D_5KP_P10.16mm_Horizontal +Diode_THT:D_5KP_P12.70mm_Horizontal +Diode_THT:D_5KP_P7.62mm_Vertical_AnodeUp +Diode_THT:D_5KP_P7.62mm_Vertical_KathodeUp +Diode_THT:D_5W_P10.16mm_Horizontal +Diode_THT:D_5W_P12.70mm_Horizontal +Diode_THT:D_5W_P5.08mm_Vertical_AnodeUp +Diode_THT:D_5W_P5.08mm_Vertical_KathodeUp +Diode_THT:D_A-405_P10.16mm_Horizontal +Diode_THT:D_A-405_P12.70mm_Horizontal +Diode_THT:D_A-405_P2.54mm_Vertical_AnodeUp +Diode_THT:D_A-405_P2.54mm_Vertical_KathodeUp +Diode_THT:D_A-405_P5.08mm_Vertical_AnodeUp +Diode_THT:D_A-405_P5.08mm_Vertical_KathodeUp +Diode_THT:D_A-405_P7.62mm_Horizontal +Diode_THT:D_DO-15_P10.16mm_Horizontal +Diode_THT:D_DO-15_P12.70mm_Horizontal +Diode_THT:D_DO-15_P15.24mm_Horizontal +Diode_THT:D_DO-15_P2.54mm_Vertical_AnodeUp +Diode_THT:D_DO-15_P2.54mm_Vertical_KathodeUp +Diode_THT:D_DO-15_P3.81mm_Vertical_AnodeUp +Diode_THT:D_DO-15_P3.81mm_Vertical_KathodeUp +Diode_THT:D_DO-15_P5.08mm_Vertical_AnodeUp +Diode_THT:D_DO-15_P5.08mm_Vertical_KathodeUp +Diode_THT:D_DO-201AD_P12.70mm_Horizontal +Diode_THT:D_DO-201AD_P15.24mm_Horizontal +Diode_THT:D_DO-201AD_P3.81mm_Vertical_AnodeUp +Diode_THT:D_DO-201AD_P3.81mm_Vertical_KathodeUp +Diode_THT:D_DO-201AD_P5.08mm_Vertical_AnodeUp +Diode_THT:D_DO-201AD_P5.08mm_Vertical_KathodeUp +Diode_THT:D_DO-201AE_P12.70mm_Horizontal +Diode_THT:D_DO-201AE_P15.24mm_Horizontal +Diode_THT:D_DO-201AE_P3.81mm_Vertical_AnodeUp +Diode_THT:D_DO-201AE_P3.81mm_Vertical_KathodeUp +Diode_THT:D_DO-201AE_P5.08mm_Vertical_AnodeUp +Diode_THT:D_DO-201AE_P5.08mm_Vertical_KathodeUp +Diode_THT:D_DO-201_P12.70mm_Horizontal +Diode_THT:D_DO-201_P15.24mm_Horizontal +Diode_THT:D_DO-201_P3.81mm_Vertical_AnodeUp +Diode_THT:D_DO-201_P3.81mm_Vertical_KathodeUp +Diode_THT:D_DO-201_P5.08mm_Vertical_AnodeUp +Diode_THT:D_DO-201_P5.08mm_Vertical_KathodeUp +Diode_THT:D_DO-247_Horizontal_TabDown +Diode_THT:D_DO-247_Horizontal_TabUp +Diode_THT:D_DO-247_Vertical +Diode_THT:D_DO-27_P12.70mm_Horizontal +Diode_THT:D_DO-27_P15.24mm_Horizontal +Diode_THT:D_DO-27_P5.08mm_Vertical_AnodeUp +Diode_THT:D_DO-27_P5.08mm_Vertical_KathodeUp +Diode_THT:D_DO-34_SOD68_P10.16mm_Horizontal +Diode_THT:D_DO-34_SOD68_P12.70mm_Horizontal +Diode_THT:D_DO-34_SOD68_P2.54mm_Vertical_AnodeUp +Diode_THT:D_DO-34_SOD68_P2.54mm_Vertical_KathodeUp +Diode_THT:D_DO-34_SOD68_P5.08mm_Vertical_AnodeUp +Diode_THT:D_DO-34_SOD68_P5.08mm_Vertical_KathodeUp +Diode_THT:D_DO-34_SOD68_P7.62mm_Horizontal +Diode_THT:D_DO-35_SOD27_P10.16mm_Horizontal +Diode_THT:D_DO-35_SOD27_P12.70mm_Horizontal +Diode_THT:D_DO-35_SOD27_P2.54mm_Vertical_AnodeUp +Diode_THT:D_DO-35_SOD27_P2.54mm_Vertical_KathodeUp +Diode_THT:D_DO-35_SOD27_P3.81mm_Vertical_AnodeUp +Diode_THT:D_DO-35_SOD27_P3.81mm_Vertical_KathodeUp +Diode_THT:D_DO-35_SOD27_P5.08mm_Vertical_AnodeUp +Diode_THT:D_DO-35_SOD27_P5.08mm_Vertical_KathodeUp +Diode_THT:D_DO-35_SOD27_P7.62mm_Horizontal +Diode_THT:D_DO-41_SOD81_P10.16mm_Horizontal +Diode_THT:D_DO-41_SOD81_P12.70mm_Horizontal +Diode_THT:D_DO-41_SOD81_P2.54mm_Vertical_AnodeUp +Diode_THT:D_DO-41_SOD81_P2.54mm_Vertical_KathodeUp +Diode_THT:D_DO-41_SOD81_P3.81mm_Vertical_AnodeUp +Diode_THT:D_DO-41_SOD81_P3.81mm_Vertical_KathodeUp +Diode_THT:D_DO-41_SOD81_P5.08mm_Vertical_AnodeUp +Diode_THT:D_DO-41_SOD81_P5.08mm_Vertical_KathodeUp +Diode_THT:D_DO-41_SOD81_P7.62mm_Horizontal +Diode_THT:D_P600_R-6_P12.70mm_Horizontal +Diode_THT:D_P600_R-6_P20.00mm_Horizontal +Diode_THT:D_P600_R-6_P7.62mm_Vertical_AnodeUp +Diode_THT:D_P600_R-6_P7.62mm_Vertical_KathodeUp +Diode_THT:D_T-1_P10.16mm_Horizontal +Diode_THT:D_T-1_P12.70mm_Horizontal +Diode_THT:D_T-1_P2.54mm_Vertical_AnodeUp +Diode_THT:D_T-1_P2.54mm_Vertical_KathodeUp +Diode_THT:D_T-1_P5.08mm_Horizontal +Display:Adafruit_SSD1306 +Display:Adafruit_SSD1306_No_Mounting_Holes +Display:AG12864E +Display:CR2013-MI2120 +Display:EA-eDIP128B-XXX +Display:EA_DOGL128-6 +Display:EA_DOGM128-6 +Display:EA_DOGS104X-A +Display:EA_DOGXL160-7 +Display:EA_DOGXL160-7_Backlight +Display:EA_eDIP160-XXX +Display:EA_eDIP240-XXX +Display:EA_eDIP320X-XXX +Display:EA_eDIPTFT32-XXX +Display:EA_eDIPTFT43-ATC +Display:EA_eDIPTFT43-XXX +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 +Display:HDSP-4830 +Display:HDSP-4832 +Display:HDSP-4836 +Display:HDSP-4840 +Display:HDSP-4850 +Display:HDSP-48xx +Display:HLCP-J100 +Display:HY1602E +Display:LCD-016N002L +Display:LM16255 +Display:NHD-0420H1Z +Display:NHD-C0220BiZ-FSRGB +Display:NHD-C0220BiZ +Display:NHD-C12832A1Z-FSRGB +Display:OLED-128O064D +Display:RC1602A +Display:WC1602A +Display_7Segment:7SEGMENT-LED__HDSM531_HDSM533_SMD +Display_7Segment:7SegmentLED_LTS6760_LTS6780 +Display_7Segment:AD-121F2 +Display_7Segment:AFF_2x7SEG-DIGIT_10mm +Display_7Segment:CA56-12CGKWA +Display_7Segment:CA56-12EWA +Display_7Segment:CA56-12SEKWA +Display_7Segment:CA56-12SRWA +Display_7Segment:CA56-12SURKWA +Display_7Segment:CA56-12SYKWA +Display_7Segment:CC56-12GWA +Display_7Segment:CC56-12YWA +Display_7Segment:D1X8K +Display_7Segment:DA04-11CGKWA +Display_7Segment:DA04-11SEKWA +Display_7Segment:DA04-11SURKWA +Display_7Segment:DA04-11SYKWA +Display_7Segment:DA56-11CGKWA +Display_7Segment:DA56-11SEKWA +Display_7Segment:DA56-11SURKWA +Display_7Segment:DA56-11SYKWA +Display_7Segment:DE113-XX-XX +Display_7Segment:DE114-RS-20 +Display_7Segment:DE119-XX-XX +Display_7Segment:DE122-XX-XX +Display_7Segment:DE152-XX-XX +Display_7Segment:DE170-XX-XX +Display_7Segment:ELD_426XXXX +Display_7Segment:HDSP-7401 +Display_7Segment:HDSP-7507 +Display_7Segment:HDSP-7801 +Display_7Segment:HDSP-7807 +Display_7Segment:HDSP-A151 +Display_7Segment:HDSP-A401 +Display_7Segment:KCSC02-105 +Display_7Segment:KCSC02-106 +Display_7Segment:KCSC02-107 +Display_7Segment:KCSC02-123 +Display_7Segment:KCSC02-136 +Display_7Segment:LTC-4627Jx +Display_7Segment:MAN3410A +Display_7Segment:MAN3420A +Display_7Segment:MAN3610A +Display_7Segment:MAN3620A +Display_7Segment:MAN3630A +Display_7Segment:MAN3810A +Display_7Segment:MAN3820A +Display_7Segment:MAN71A +Display_7Segment:MAN72A +Display_7Segment:MAN73A +Display_7Segment:SA15-11xxx +Display_7Segment:SBC18-11SURKCGKWA +Display_7Segment:Sx39-1xxxxx +Ferrite_THT:LairdTech_28C0236-0JW-10 +Fiducial:Fiducial_0.5mm_Mask1.5mm +Fiducial:Fiducial_0.5mm_Mask1mm +Fiducial:Fiducial_0.75mm_Mask1.5mm +Fiducial:Fiducial_0.75mm_Mask2.25mm +Fiducial:Fiducial_1.5mm_Mask3mm +Fiducial:Fiducial_1.5mm_Mask4.5mm +Fiducial:Fiducial_1mm_Mask2mm +Fiducial:Fiducial_1mm_Mask3mm +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 +Filter:Filter_Mini-Circuits_FV1206-6 +Filter:Filter_Mini-Circuits_FV1206-7 +Filter:Filter_Mini-Circuits_FV1206 +Filter:Filter_Murata_BNX025 +Filter:Filter_Murata_BNX025_ThermalVias +Filter:Filter_Murata_SFECF-6 +Filter:Filter_Murata_SFECF-6_HandSoldering +Filter:Filter_SAW-6_3.8x3.8mm +Filter:Filter_SAW-8_3.8x3.8mm +Filter:Filter_SAW_Epcos_DCC6C_3x3mm +Filter:Filter_Schaffner_FN405 +Filter:Filter_Schaffner_FN406 +Fuse:FuseHolder_Blade_ATO_Littelfuse_FLR_178.6165 +Fuse:Fuseholder_Blade_ATO_Littelfuse_Pudenz_2_Pin +Fuse:Fuseholder_Blade_Mini_Keystone_3568 +Fuse:Fuseholder_Clip-5x20mm_Bel_FC-203-22_Lateral_P17.80x5.00mm_D1.17mm_Horizontal +Fuse:Fuseholder_Clip-5x20mm_Eaton_1A5601-01_Inline_P20.80x6.76mm_D1.70mm_Horizontal +Fuse:Fuseholder_Clip-5x20mm_Keystone_3512P_Inline_P23.62x7.27mm_D1.02x2.41x1.02x1.57mm_Horizontal +Fuse:Fuseholder_Clip-5x20mm_Keystone_3512_Inline_P23.62x7.27mm_D1.02x1.57mm_Horizontal +Fuse:Fuseholder_Clip-5x20mm_Keystone_3517_Inline_P23.11x6.76mm_D1.70mm_Horizontal +Fuse:Fuseholder_Clip-5x20mm_Keystone_3518P_Inline_P23.11x6.76mm_D2.44x1.70mm_Horizontal +Fuse:Fuseholder_Clip-5x20mm_Littelfuse_100_Inline_P20.50x4.60mm_D1.30mm_Horizontal +Fuse:Fuseholder_Clip-5x20mm_Littelfuse_111_Inline_P20.00x5.00mm_D1.05mm_Horizontal +Fuse:Fuseholder_Clip-5x20mm_Littelfuse_111_Lateral_P18.80x5.00mm_D1.17mm_Horizontal +Fuse:Fuseholder_Clip-5x20mm_Littelfuse_445-030_Inline_P20.50x5.20mm_D1.30mm_Horizontal +Fuse:Fuseholder_Clip-5x20mm_Littelfuse_519_Inline_P20.60x5.00mm_D1.00mm_Horizontal +Fuse:Fuseholder_Clip-5x20mm_Littelfuse_520_Inline_P20.50x5.80mm_D1.30mm_Horizontal +Fuse:Fuseholder_Clip-5x20mm_Littelfuse_521_Lateral_P17.00x5.00mm_D1.30mm_Horizontal +Fuse:Fuseholder_Clip-5x20mm_Schurter_CQM_Inline_P20.60x5.00mm_D1.00mm_Horizontal +Fuse:Fuseholder_Clip-5x20mm_Schurter_OG_Lateral_P15.00x5.00mm_D1.3mm_Horizontal +Fuse:Fuseholder_Clip-6.3x32mm_Littelfuse_102071_Inline_P34.70x7.60mm_D2.00mm_Horizontal +Fuse:Fuseholder_Clip-6.3x32mm_Littelfuse_102_122_Inline_P34.21x7.62mm_D1.98mm_Horizontal +Fuse:Fuseholder_Clip-6.3x32mm_Littelfuse_102_Inline_P34.21x7.62mm_D2.54mm_Horizontal +Fuse:Fuseholder_Clip-6.3x32mm_Littelfuse_122_Inline_P34.21x7.62mm_D2.54mm_Horizontal +Fuse:Fuseholder_Cylinder-5x20mm_Bulgin_FX0456_Vertical_Closed +Fuse:Fuseholder_Cylinder-5x20mm_Bulgin_FX0457_Horizontal_Closed +Fuse:Fuseholder_Cylinder-5x20mm_EATON_H15-V-1_Vertical_Closed +Fuse:Fuseholder_Cylinder-5x20mm_EATON_HBV_Vertical_Closed +Fuse:Fuseholder_Cylinder-5x20mm_EATON_HBW_Vertical_Closed +Fuse:Fuseholder_Cylinder-5x20mm_Schurter_0031_8201_Horizontal_Open +Fuse:Fuseholder_Cylinder-5x20mm_Schurter_FAB_0031-355x_Horizontal_Closed +Fuse:Fuseholder_Cylinder-5x20mm_Schurter_FPG4_Vertical_Closed +Fuse:Fuseholder_Cylinder-5x20mm_Schurter_FUP_0031.2510_Horizontal_Closed +Fuse:Fuseholder_Cylinder-5x20mm_Schurter_OGN-SMD_Horizontal_Open +Fuse:Fuseholder_Cylinder-5x20mm_Stelvio-Kontek_PTF78_Horizontal_Open +Fuse:Fuseholder_Cylinder-5x20mm_Wuerth_696103101002-SMD_Horizontal_Open +Fuse:Fuseholder_Cylinder-6.3x32mm_Schurter_0031-8002_Horizontal_Open +Fuse:Fuseholder_Cylinder-6.3x32mm_Schurter_FUP_0031.2520_Horizontal_Closed +Fuse:Fuseholder_Keystone_3555-2 +Fuse:Fuseholder_Littelfuse_100_series_5x20mm +Fuse:Fuseholder_Littelfuse_100_series_5x25mm +Fuse:Fuseholder_Littelfuse_100_series_5x30mm +Fuse:Fuseholder_Littelfuse_445_030_series_5x20mm +Fuse:Fuseholder_Littelfuse_445_030_series_5x25mm +Fuse:Fuseholder_Littelfuse_445_030_series_5x30mm +Fuse:Fuseholder_Littelfuse_Nano2_154x +Fuse:Fuseholder_Littelfuse_Nano2_157x +Fuse:Fuseholder_TR5_Littelfuse_No560_No460 +Fuse:Fuse_0402_1005Metric +Fuse:Fuse_0402_1005Metric_Pad0.77x0.64mm_HandSolder +Fuse:Fuse_0603_1608Metric +Fuse:Fuse_0603_1608Metric_Pad1.05x0.95mm_HandSolder +Fuse:Fuse_0805_2012Metric +Fuse:Fuse_0805_2012Metric_Pad1.15x1.40mm_HandSolder +Fuse:Fuse_1206_3216Metric +Fuse:Fuse_1206_3216Metric_Pad1.42x1.75mm_HandSolder +Fuse:Fuse_1210_3225Metric +Fuse:Fuse_1210_3225Metric_Pad1.42x2.65mm_HandSolder +Fuse:Fuse_1812_4532Metric +Fuse:Fuse_1812_4532Metric_Pad1.30x3.40mm_HandSolder +Fuse:Fuse_2010_5025Metric +Fuse:Fuse_2010_5025Metric_Pad1.52x2.65mm_HandSolder +Fuse:Fuse_2512_6332Metric +Fuse:Fuse_2512_6332Metric_Pad1.52x3.35mm_HandSolder +Fuse:Fuse_2920_7451Metric +Fuse:Fuse_2920_7451Metric_Pad2.10x5.45mm_HandSolder +Fuse:Fuse_BelFuse_0ZRE0005FF_L8.3mm_W3.8mm +Fuse:Fuse_BelFuse_0ZRE0008FF_L8.3mm_W3.8mm +Fuse:Fuse_BelFuse_0ZRE0012FF_L8.3mm_W3.8mm +Fuse:Fuse_BelFuse_0ZRE0016FF_L9.9mm_W3.8mm +Fuse:Fuse_BelFuse_0ZRE0025FF_L9.6mm_W3.8mm +Fuse:Fuse_BelFuse_0ZRE0033FF_L11.4mm_W3.8mm +Fuse:Fuse_BelFuse_0ZRE0040FF_L11.5mm_W3.8mm +Fuse:Fuse_BelFuse_0ZRE0055FF_L14.0mm_W4.1mm +Fuse:Fuse_BelFuse_0ZRE0075FF_L11.5mm_W4.8mm +Fuse:Fuse_BelFuse_0ZRE0100FF_L18.7mm_W5.1mm +Fuse:Fuse_BelFuse_0ZRE0125FF_L21.2mm_W5.3mm +Fuse:Fuse_BelFuse_0ZRE0150FF_L23.4mm_W5.3mm +Fuse:Fuse_BelFuse_0ZRE0200FF_L24.9mm_W6.1mm +Fuse:Fuse_Blade_ATO_directSolder +Fuse:Fuse_Blade_Mini_directSolder +Fuse:Fuse_Bourns_MF-RG1000 +Fuse:Fuse_Bourns_MF-RG1100 +Fuse:Fuse_Bourns_MF-RG300 +Fuse:Fuse_Bourns_MF-RG400 +Fuse:Fuse_Bourns_MF-RG500 +Fuse:Fuse_Bourns_MF-RG600 +Fuse:Fuse_Bourns_MF-RG650 +Fuse:Fuse_Bourns_MF-RG700 +Fuse:Fuse_Bourns_MF-RG800 +Fuse:Fuse_Bourns_MF-RG900 +Fuse:Fuse_Bourns_MF-RHT050 +Fuse:Fuse_Bourns_MF-RHT070 +Fuse:Fuse_Bourns_MF-RHT100 +Fuse:Fuse_Bourns_MF-RHT1000 +Fuse:Fuse_Bourns_MF-RHT1100 +Fuse:Fuse_Bourns_MF-RHT1300 +Fuse:Fuse_Bourns_MF-RHT200 +Fuse:Fuse_Bourns_MF-RHT300 +Fuse:Fuse_Bourns_MF-RHT400 +Fuse:Fuse_Bourns_MF-RHT500 +Fuse:Fuse_Bourns_MF-RHT550 +Fuse:Fuse_Bourns_MF-RHT600 +Fuse:Fuse_Bourns_MF-RHT650 +Fuse:Fuse_Bourns_MF-RHT700 +Fuse:Fuse_Bourns_MF-RHT750 +Fuse:Fuse_Bourns_MF-RHT800 +Fuse:Fuse_Bourns_MF-RHT900 +Fuse:Fuse_Bourns_MF-SM_7.98x5.44mm +Fuse:Fuse_Bourns_MF-SM_9.5x6.71mm +Fuse:Fuse_Bourns_TBU-CA +Fuse:Fuse_Littelfuse-LVR100 +Fuse:Fuse_Littelfuse-LVR125 +Fuse:Fuse_Littelfuse-LVR200 +Fuse:Fuse_Littelfuse-NANO2-451_453 +Fuse:Fuse_Littelfuse-NANO2-462 +Fuse:Fuse_Littelfuse-NANO2-885 +Fuse:Fuse_Littelfuse_372_D8.50mm +Fuse:Fuse_Littelfuse_395Series +Fuse:Fuse_Schurter_UMT250 +Fuse:Fuse_Schurter_UMZ250 +Fuse:Fuse_SunFuse-6HP +Heatsink:Heatsink_125x35x50mm_3xFixationM3 +Heatsink:Heatsink_35x26mm_1xFixation3mm_Fischer-SK486-35 +Heatsink:Heatsink_38x38mm_SpringFixation +Heatsink:Heatsink_62x40mm_2xFixation3mm +Heatsink:Heatsink_AAVID_573300D00010G_TO-263 +Heatsink:Heatsink_AAVID_576802B03900G +Heatsink:Heatsink_AAVID_590302B03600G +Heatsink:Heatsink_AAVID_TV5G_TO220_Horizontal +Heatsink:Heatsink_Fischer_FK224xx2201_25x8.3mm +Heatsink:Heatsink_Fischer_FK24413D2PAK_26x13mm +Heatsink:Heatsink_Fischer_FK24413DPAK_23x13mm +Heatsink:Heatsink_Fischer_SK104-STC-STIC_35x13mm_2xDrill2.5mm +Heatsink:Heatsink_Fischer_SK104-STCB_35x13mm__2xDrill3.5mm_ScrewM3 +Heatsink:Heatsink_Fischer_SK129-STS_42x25mm_2xDrill2.5mm +Heatsink:Heatsink_SheetType_50x7mm_2Fixations +Heatsink:Heatsink_Stonecold_HS-130_30x12mm_2xFixation2.5mm +Heatsink:Heatsink_Stonecold_HS-132_32x14mm_2xFixation1.5mm +Heatsink:Heatsink_Stonecold_HS-S01_13.21x6.35mm +Heatsink:Heatsink_Stonecold_HS-S02_13.21x9.53mm +Heatsink:Heatsink_Stonecold_HS-S03_13.21x12.7mm +Inductor_SMD:L_01005_0402Metric +Inductor_SMD:L_01005_0402Metric_Pad0.57x0.30mm_HandSolder +Inductor_SMD:L_0201_0603Metric +Inductor_SMD:L_0201_0603Metric_Pad0.64x0.40mm_HandSolder +Inductor_SMD:L_0402_1005Metric +Inductor_SMD:L_0402_1005Metric_Pad0.77x0.64mm_HandSolder +Inductor_SMD:L_0603_1608Metric +Inductor_SMD:L_0603_1608Metric_Pad1.05x0.95mm_HandSolder +Inductor_SMD:L_0805_2012Metric +Inductor_SMD:L_0805_2012Metric_Pad1.05x1.20mm_HandSolder +Inductor_SMD:L_0805_2012Metric_Pad1.15x1.40mm_HandSolder +Inductor_SMD:L_10.4x10.4_H4.8 +Inductor_SMD:L_1008_2520Metric +Inductor_SMD:L_1008_2520Metric_Pad1.43x2.20mm_HandSolder +Inductor_SMD:L_1206_3216Metric +Inductor_SMD:L_1206_3216Metric_Pad1.22x1.90mm_HandSolder +Inductor_SMD:L_1206_3216Metric_Pad1.42x1.75mm_HandSolder +Inductor_SMD:L_1210_3225Metric +Inductor_SMD:L_1210_3225Metric_Pad1.42x2.65mm_HandSolder +Inductor_SMD:L_12x12mm_H4.5mm +Inductor_SMD:L_12x12mm_H6mm +Inductor_SMD:L_12x12mm_H8mm +Inductor_SMD:L_1806_4516Metric +Inductor_SMD:L_1806_4516Metric_Pad1.45x1.90mm_HandSolder +Inductor_SMD:L_1812_4532Metric +Inductor_SMD:L_1812_4532Metric_Pad1.30x3.40mm_HandSolder +Inductor_SMD:L_2010_5025Metric +Inductor_SMD:L_2010_5025Metric_Pad1.52x2.65mm_HandSolder +Inductor_SMD:L_2512_6332Metric +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 +Inductor_SMD:L_Bourns-SRN6028 +Inductor_SMD:L_Bourns-SRN8040_8x8.15mm +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 +Inductor_SMD:L_Changjiang_FNR3012S +Inductor_SMD:L_Changjiang_FNR3015S +Inductor_SMD:L_Changjiang_FNR3021S +Inductor_SMD:L_Changjiang_FNR4010S +Inductor_SMD:L_Changjiang_FNR4012S +Inductor_SMD:L_Changjiang_FNR4015S +Inductor_SMD:L_Changjiang_FNR4018S +Inductor_SMD:L_Changjiang_FNR4020S +Inductor_SMD:L_Changjiang_FNR4026S +Inductor_SMD:L_Changjiang_FNR4030S +Inductor_SMD:L_Changjiang_FNR5012S +Inductor_SMD:L_Changjiang_FNR5015S +Inductor_SMD:L_Changjiang_FNR5020S +Inductor_SMD:L_Changjiang_FNR5030S +Inductor_SMD:L_Changjiang_FNR5040S +Inductor_SMD:L_Changjiang_FNR5045S +Inductor_SMD:L_Changjiang_FNR6020S +Inductor_SMD:L_Changjiang_FNR6028S +Inductor_SMD:L_Changjiang_FNR6040S +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 +Inductor_SMD:L_Chilisin_BMRA00050530 +Inductor_SMD:L_Chilisin_BMRB00050512 +Inductor_SMD:L_Chilisin_BMRB00050518-B +Inductor_SMD:L_Chilisin_BMRB00050518 +Inductor_SMD:L_Chilisin_BMRB00060612 +Inductor_SMD:L_Chilisin_BMRB00060618 +Inductor_SMD:L_Chilisin_BMRB00060624 +Inductor_SMD:L_Chilisin_BMRB00060650 +Inductor_SMD:L_Chilisin_BMRF00101040 +Inductor_SMD:L_Chilisin_BMRF00131350 +Inductor_SMD:L_Chilisin_BMRF00131360 +Inductor_SMD:L_Chilisin_BMRF00171770 +Inductor_SMD:L_Chilisin_BMRG00101030 +Inductor_SMD:L_Chilisin_BMRG00131360 +Inductor_SMD:L_Chilisin_BMRx00040412 +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 +Inductor_SMD:L_Coilcraft_XAL1350-XXX +Inductor_SMD:L_Coilcraft_XAL1510-103 +Inductor_SMD:L_Coilcraft_XAL1510-153 +Inductor_SMD:L_Coilcraft_XAL1510-223 +Inductor_SMD:L_Coilcraft_XAL1510-333 +Inductor_SMD:L_Coilcraft_XAL1510-472 +Inductor_SMD:L_Coilcraft_XAL1510-682 +Inductor_SMD:L_Coilcraft_XAL1510-822 +Inductor_SMD:L_Coilcraft_XAL1513-153 +Inductor_SMD:L_Coilcraft_XAL1580-102 +Inductor_SMD:L_Coilcraft_XAL1580-132 +Inductor_SMD:L_Coilcraft_XAL1580-182 +Inductor_SMD:L_Coilcraft_XAL1580-202 +Inductor_SMD:L_Coilcraft_XAL1580-302 +Inductor_SMD:L_Coilcraft_XAL1580-401 +Inductor_SMD:L_Coilcraft_XAL1580-452 +Inductor_SMD:L_Coilcraft_XAL1580-532 +Inductor_SMD:L_Coilcraft_XAL1580-612 +Inductor_SMD:L_Coilcraft_XAL1580-741 +Inductor_SMD:L_Coilcraft_XAL4020-XXX +Inductor_SMD:L_Coilcraft_XAL4030-XXX +Inductor_SMD:L_Coilcraft_XAL4040-XXX +Inductor_SMD:L_Coilcraft_XAL5020-XXX +Inductor_SMD:L_Coilcraft_XAL5030-XXX +Inductor_SMD:L_Coilcraft_XAL5050-XXX +Inductor_SMD:L_Coilcraft_XAL6020-XXX +Inductor_SMD:L_Coilcraft_XAL6030-XXX +Inductor_SMD:L_Coilcraft_XAL6060-XXX +Inductor_SMD:L_Coilcraft_XAL7020-102 +Inductor_SMD:L_Coilcraft_XAL7020-122 +Inductor_SMD:L_Coilcraft_XAL7020-151 +Inductor_SMD:L_Coilcraft_XAL7020-152 +Inductor_SMD:L_Coilcraft_XAL7020-222 +Inductor_SMD:L_Coilcraft_XAL7020-271 +Inductor_SMD:L_Coilcraft_XAL7020-331 +Inductor_SMD:L_Coilcraft_XAL7020-471 +Inductor_SMD:L_Coilcraft_XAL7020-681 +Inductor_SMD:L_Coilcraft_XAL7030-102 +Inductor_SMD:L_Coilcraft_XAL7030-103 +Inductor_SMD:L_Coilcraft_XAL7030-152 +Inductor_SMD:L_Coilcraft_XAL7030-161 +Inductor_SMD:L_Coilcraft_XAL7030-222 +Inductor_SMD:L_Coilcraft_XAL7030-272 +Inductor_SMD:L_Coilcraft_XAL7030-301 +Inductor_SMD:L_Coilcraft_XAL7030-332 +Inductor_SMD:L_Coilcraft_XAL7030-472 +Inductor_SMD:L_Coilcraft_XAL7030-562 +Inductor_SMD:L_Coilcraft_XAL7030-601 +Inductor_SMD:L_Coilcraft_XAL7030-682 +Inductor_SMD:L_Coilcraft_XAL7030-822 +Inductor_SMD:L_Coilcraft_XAL7050-XXX +Inductor_SMD:L_Coilcraft_XAL7070-XXX +Inductor_SMD:L_Coilcraft_XAL8050-223 +Inductor_SMD:L_Coilcraft_XAL8080-XXX +Inductor_SMD:L_Coilcraft_XFL2010 +Inductor_SMD:L_Coilcraft_XxL4020 +Inductor_SMD:L_Coilcraft_XxL4030 +Inductor_SMD:L_Coilcraft_XxL4040 +Inductor_SMD:L_CommonModeChoke_Coilcraft_0603USB +Inductor_SMD:L_CommonModeChoke_Coilcraft_0805USB +Inductor_SMD:L_CommonModeChoke_Coilcraft_1812CAN +Inductor_SMD:L_CommonModeChoke_Murata_DLW5BTMxxxSQ2x_5x5mm +Inductor_SMD:L_CommonModeChoke_TDK_ACM2520-2P +Inductor_SMD:L_CommonModeChoke_TDK_ACM2520-3P +Inductor_SMD:L_CommonModeChoke_TDK_ACM7060 +Inductor_SMD:L_CommonModeChoke_Wuerth_WE-SL5 +Inductor_SMD:L_CommonMode_Delevan_4222 +Inductor_SMD:L_CommonMode_Wuerth_WE-SL2 +Inductor_SMD:L_CommonMode_Wurth_WE-CNSW-1206 +Inductor_SMD:L_Eaton_MCL2012V1 +Inductor_SMD:L_Fastron_PISN +Inductor_SMD:L_Fastron_PISN_Handsoldering +Inductor_SMD:L_Fastron_PISR +Inductor_SMD:L_Fastron_PISR_Handsoldering +Inductor_SMD:L_Ferrocore_DLG-0302 +Inductor_SMD:L_Ferrocore_DLG-0403 +Inductor_SMD:L_Ferrocore_DLG-0504 +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 +Inductor_SMD:L_Murata_LQH55DN_5.7x5.0mm +Inductor_SMD:L_Neosid_Air-Coil_SML_1turn_HDM0131A +Inductor_SMD:L_Neosid_Air-Coil_SML_2turn_HAM0231A +Inductor_SMD:L_Neosid_Air-Coil_SML_2turn_HDM0231A +Inductor_SMD:L_Neosid_Air-Coil_SML_3turn_HAM0331A +Inductor_SMD:L_Neosid_Air-Coil_SML_3turn_HDM0331A +Inductor_SMD:L_Neosid_Air-Coil_SML_4turn_HAM0431A +Inductor_SMD:L_Neosid_Air-Coil_SML_4turn_HDM0431A +Inductor_SMD:L_Neosid_Air-Coil_SML_5turn_HAM0531A +Inductor_SMD:L_Neosid_Air-Coil_SML_5turn_HDM0531A +Inductor_SMD:L_Neosid_Air-Coil_SML_6-10turn_HAM0631A-HAM1031A +Inductor_SMD:L_Neosid_Air-Coil_SML_6-10turn_HDM0431A-HDM1031A +Inductor_SMD:L_Neosid_Air-Coil_SML_6turn_HAM0631A +Inductor_SMD:L_Neosid_MicroCoil_Ms36-L +Inductor_SMD:L_Neosid_Ms42 +Inductor_SMD:L_Neosid_Ms50 +Inductor_SMD:L_Neosid_Ms50T +Inductor_SMD:L_Neosid_Ms85 +Inductor_SMD:L_Neosid_Ms85T +Inductor_SMD:L_Neosid_Ms95 +Inductor_SMD:L_Neosid_Ms95a +Inductor_SMD:L_Neosid_Ms95T +Inductor_SMD:L_Neosid_SM-NE127 +Inductor_SMD:L_Neosid_SM-NE127_HandSoldering +Inductor_SMD:L_Neosid_SM-NE150 +Inductor_SMD:L_Neosid_SM-NE95H +Inductor_SMD:L_Neosid_SM-PIC0512H +Inductor_SMD:L_Neosid_SM-PIC0602H +Inductor_SMD:L_Neosid_SM-PIC0612H +Inductor_SMD:L_Neosid_SM-PIC1004H +Inductor_SMD:L_Neosid_SMS-ME3010 +Inductor_SMD:L_Neosid_SMS-ME3015 +Inductor_SMD:L_Neosid_SMs42 +Inductor_SMD:L_Neosid_SMs50 +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 +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 +Inductor_SMD:L_Sunlord_MWSA0412S +Inductor_SMD:L_Sunlord_MWSA0503S +Inductor_SMD:L_Sunlord_MWSA0518S +Inductor_SMD:L_Sunlord_MWSA0602S +Inductor_SMD:L_Sunlord_MWSA0603S +Inductor_SMD:L_Sunlord_MWSA0604S +Inductor_SMD:L_Sunlord_MWSA0605S +Inductor_SMD:L_Sunlord_MWSA0615S +Inductor_SMD:L_Sunlord_MWSA0618S +Inductor_SMD:L_Sunlord_MWSA0624S +Inductor_SMD:L_Sunlord_MWSA0804S +Inductor_SMD:L_Sunlord_MWSA1003S +Inductor_SMD:L_Sunlord_MWSA1004S +Inductor_SMD:L_Sunlord_MWSA1005S +Inductor_SMD:L_Sunlord_MWSA1204S-100 +Inductor_SMD:L_Sunlord_MWSA1204S-150 +Inductor_SMD:L_Sunlord_MWSA1204S-1R0 +Inductor_SMD:L_Sunlord_MWSA1204S-1R5 +Inductor_SMD:L_Sunlord_MWSA1204S-220 +Inductor_SMD:L_Sunlord_MWSA1204S-2R2 +Inductor_SMD:L_Sunlord_MWSA1204S-3R3 +Inductor_SMD:L_Sunlord_MWSA1204S-4R7 +Inductor_SMD:L_Sunlord_MWSA1204S-6R8 +Inductor_SMD:L_Sunlord_MWSA1204S-R22 +Inductor_SMD:L_Sunlord_MWSA1204S-R47 +Inductor_SMD:L_Sunlord_MWSA1204S-R68 +Inductor_SMD:L_Sunlord_MWSA1204S-R82 +Inductor_SMD:L_Sunlord_MWSA1205S-100 +Inductor_SMD:L_Sunlord_MWSA1205S-150 +Inductor_SMD:L_Sunlord_MWSA1205S-1R0 +Inductor_SMD:L_Sunlord_MWSA1205S-1R5 +Inductor_SMD:L_Sunlord_MWSA1205S-220 +Inductor_SMD:L_Sunlord_MWSA1205S-2R2 +Inductor_SMD:L_Sunlord_MWSA1205S-330 +Inductor_SMD:L_Sunlord_MWSA1205S-3R3 +Inductor_SMD:L_Sunlord_MWSA1205S-470 +Inductor_SMD:L_Sunlord_MWSA1205S-4R7 +Inductor_SMD:L_Sunlord_MWSA1205S-6R8 +Inductor_SMD:L_Sunlord_MWSA1205S-R22 +Inductor_SMD:L_Sunlord_MWSA1205S-R36 +Inductor_SMD:L_Sunlord_MWSA1205S-R50 +Inductor_SMD:L_Sunlord_MWSA1205S-R68 +Inductor_SMD:L_Sunlord_MWSA1205S-R82 +Inductor_SMD:L_Sunlord_MWSA1206S-100 +Inductor_SMD:L_Sunlord_MWSA1206S-101 +Inductor_SMD:L_Sunlord_MWSA1206S-120 +Inductor_SMD:L_Sunlord_MWSA1206S-121 +Inductor_SMD:L_Sunlord_MWSA1206S-150 +Inductor_SMD:L_Sunlord_MWSA1206S-151 +Inductor_SMD:L_Sunlord_MWSA1206S-180 +Inductor_SMD:L_Sunlord_MWSA1206S-1R5 +Inductor_SMD:L_Sunlord_MWSA1206S-220 +Inductor_SMD:L_Sunlord_MWSA1206S-270 +Inductor_SMD:L_Sunlord_MWSA1206S-2R2 +Inductor_SMD:L_Sunlord_MWSA1206S-330 +Inductor_SMD:L_Sunlord_MWSA1206S-3R3 +Inductor_SMD:L_Sunlord_MWSA1206S-470 +Inductor_SMD:L_Sunlord_MWSA1206S-4R7 +Inductor_SMD:L_Sunlord_MWSA1206S-5R6 +Inductor_SMD:L_Sunlord_MWSA1206S-680 +Inductor_SMD:L_Sunlord_MWSA1206S-6R8 +Inductor_SMD:L_Sunlord_MWSA1206S-8R2 +Inductor_SMD:L_Sunlord_MWSA1206S-R68 +Inductor_SMD:L_Sunlord_MWSA1265S +Inductor_SMD:L_Sunlord_MWSA1707S +Inductor_SMD:L_Sunlord_MWSA2213S +Inductor_SMD:L_Sunlord_SWPA252010S +Inductor_SMD:L_Sunlord_SWPA252012S +Inductor_SMD:L_Sunlord_SWPA3010S +Inductor_SMD:L_Sunlord_SWPA3012S +Inductor_SMD:L_Sunlord_SWPA3015S +Inductor_SMD:L_Sunlord_SWPA4010S +Inductor_SMD:L_Sunlord_SWPA4012S +Inductor_SMD:L_Sunlord_SWPA4018S +Inductor_SMD:L_Sunlord_SWPA4020S +Inductor_SMD:L_Sunlord_SWPA4026S +Inductor_SMD:L_Sunlord_SWPA4030S +Inductor_SMD:L_Sunlord_SWPA5012S +Inductor_SMD:L_Sunlord_SWPA5020S +Inductor_SMD:L_Sunlord_SWPA5040S +Inductor_SMD:L_Sunlord_SWPA6020S +Inductor_SMD:L_Sunlord_SWPA6028S +Inductor_SMD:L_Sunlord_SWPA6040S +Inductor_SMD:L_Sunlord_SWPA6045S +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 +Inductor_SMD:L_Taiyo-Yuden_MD-2020 +Inductor_SMD:L_Taiyo-Yuden_MD-3030 +Inductor_SMD:L_Taiyo-Yuden_MD-4040 +Inductor_SMD:L_Taiyo-Yuden_MD-5050 +Inductor_SMD:L_Taiyo-Yuden_NR-10050_9.8x10.0mm +Inductor_SMD:L_Taiyo-Yuden_NR-10050_9.8x10.0mm_HandSoldering +Inductor_SMD:L_Taiyo-Yuden_NR-20xx +Inductor_SMD:L_Taiyo-Yuden_NR-20xx_HandSoldering +Inductor_SMD:L_Taiyo-Yuden_NR-24xx +Inductor_SMD:L_Taiyo-Yuden_NR-24xx_HandSoldering +Inductor_SMD:L_Taiyo-Yuden_NR-30xx +Inductor_SMD:L_Taiyo-Yuden_NR-30xx_HandSoldering +Inductor_SMD:L_Taiyo-Yuden_NR-40xx +Inductor_SMD:L_Taiyo-Yuden_NR-40xx_HandSoldering +Inductor_SMD:L_Taiyo-Yuden_NR-50xx +Inductor_SMD:L_Taiyo-Yuden_NR-50xx_HandSoldering +Inductor_SMD:L_Taiyo-Yuden_NR-60xx +Inductor_SMD:L_Taiyo-Yuden_NR-60xx_HandSoldering +Inductor_SMD:L_Taiyo-Yuden_NR-80xx +Inductor_SMD:L_Taiyo-Yuden_NR-80xx_HandSoldering +Inductor_SMD:L_TDK_MLZ1608 +Inductor_SMD:L_TDK_MLZ2012_h0.85mm +Inductor_SMD:L_TDK_MLZ2012_h1.25mm +Inductor_SMD:L_TDK_NLV25_2.5x2.0mm +Inductor_SMD:L_TDK_NLV32_3.2x2.5mm +Inductor_SMD:L_TDK_SLF10145 +Inductor_SMD:L_TDK_SLF10165 +Inductor_SMD:L_TDK_SLF12555 +Inductor_SMD:L_TDK_SLF12565 +Inductor_SMD:L_TDK_SLF12575 +Inductor_SMD:L_TDK_SLF6025 +Inductor_SMD:L_TDK_SLF6028 +Inductor_SMD:L_TDK_SLF6045 +Inductor_SMD:L_TDK_SLF7032 +Inductor_SMD:L_TDK_SLF7045 +Inductor_SMD:L_TDK_SLF7055 +Inductor_SMD:L_TDK_VLF10040 +Inductor_SMD:L_TDK_VLP8040 +Inductor_SMD:L_TDK_VLS6045EX_VLS6045AF +Inductor_SMD:L_TracoPower_TCK-047_5.2x5.8mm +Inductor_SMD:L_TracoPower_TCK-141 +Inductor_SMD:L_Vishay_IFSC-1515AH_4x4x1.8mm +Inductor_SMD:L_Vishay_IHLP-1212 +Inductor_SMD:L_Vishay_IHLP-1616 +Inductor_SMD:L_Vishay_IHLP-2020 +Inductor_SMD:L_Vishay_IHLP-2525 +Inductor_SMD:L_Vishay_IHLP-4040 +Inductor_SMD:L_Vishay_IHLP-5050 +Inductor_SMD:L_Vishay_IHLP-6767 +Inductor_SMD:L_Vishay_IHSM-3825 +Inductor_SMD:L_Vishay_IHSM-4825 +Inductor_SMD:L_Vishay_IHSM-5832 +Inductor_SMD:L_Vishay_IHSM-7832 +Inductor_SMD:L_Walsin_WLFM201209x +Inductor_SMD:L_Walsin_WLFM201609x +Inductor_SMD:L_Walsin_WLFM252009x +Inductor_SMD:L_Wuerth_HCF-2013 +Inductor_SMD:L_Wuerth_HCF-2815 +Inductor_SMD:L_Wuerth_HCF-2818 +Inductor_SMD:L_Wuerth_HCI-1030 +Inductor_SMD:L_Wuerth_HCI-1040 +Inductor_SMD:L_Wuerth_HCI-1050 +Inductor_SMD:L_Wuerth_HCI-1335 +Inductor_SMD:L_Wuerth_HCI-1350 +Inductor_SMD:L_Wuerth_HCI-1365 +Inductor_SMD:L_Wuerth_HCI-1890 +Inductor_SMD:L_Wuerth_HCI-2212 +Inductor_SMD:L_Wuerth_HCI-5040 +Inductor_SMD:L_Wuerth_HCI-7030 +Inductor_SMD:L_Wuerth_HCI-7040 +Inductor_SMD:L_Wuerth_HCI-7050 +Inductor_SMD:L_Wuerth_HCM-1050 +Inductor_SMD:L_Wuerth_HCM-1052 +Inductor_SMD:L_Wuerth_HCM-1070 +Inductor_SMD:L_Wuerth_HCM-1078 +Inductor_SMD:L_Wuerth_HCM-1190 +Inductor_SMD:L_Wuerth_HCM-1240 +Inductor_SMD:L_Wuerth_HCM-1350 +Inductor_SMD:L_Wuerth_HCM-1390 +Inductor_SMD:L_Wuerth_HCM-7050 +Inductor_SMD:L_Wuerth_HCM-7070 +Inductor_SMD:L_Wuerth_MAPI-1610 +Inductor_SMD:L_Wuerth_MAPI-2010 +Inductor_SMD:L_Wuerth_MAPI-2506 +Inductor_SMD:L_Wuerth_MAPI-2508 +Inductor_SMD:L_Wuerth_MAPI-2510 +Inductor_SMD:L_Wuerth_MAPI-2512 +Inductor_SMD:L_Wuerth_MAPI-3010 +Inductor_SMD:L_Wuerth_MAPI-3012 +Inductor_SMD:L_Wuerth_MAPI-3015 +Inductor_SMD:L_Wuerth_MAPI-3020 +Inductor_SMD:L_Wuerth_MAPI-4020 +Inductor_SMD:L_Wuerth_MAPI-4030 +Inductor_SMD:L_Wuerth_WE-DD-Typ-L-Typ-XL-Typ-XXL +Inductor_SMD:L_Wuerth_WE-DD-Typ-M-Typ-S +Inductor_SMD:L_Wuerth_WE-GF-1210 +Inductor_SMD:L_Wuerth_WE-PD-Typ-7345 +Inductor_SMD:L_Wuerth_WE-PD-Typ-LS +Inductor_SMD:L_Wuerth_WE-PD-Typ-LS_Handsoldering +Inductor_SMD:L_Wuerth_WE-PD-Typ-M-Typ-S +Inductor_SMD:L_Wuerth_WE-PD-Typ-M-Typ-S_Handsoldering +Inductor_SMD:L_Wuerth_WE-PD2-Typ-L +Inductor_SMD:L_Wuerth_WE-PD2-Typ-MS +Inductor_SMD:L_Wuerth_WE-PD2-Typ-XL +Inductor_SMD:L_Wuerth_WE-PD4-Typ-X +Inductor_SMD:L_Wuerth_WE-PDF +Inductor_SMD:L_Wuerth_WE-PDF_Handsoldering +Inductor_SMD:L_Wuerth_WE-TPC-3816 +Inductor_SMD:L_Wuerth_WE-XHMI-8080 +Inductor_SMD:L_Wurth_WE-CAIR-5910 +Inductor_SMD_Wurth:L_Wurth_WE-LQSH-2010 +Inductor_SMD_Wurth:L_Wurth_WE-LQSH-2512 +Inductor_SMD_Wurth:L_Wurth_WE-LQSH-3012 +Inductor_SMD_Wurth:L_Wurth_WE-LQSH-4020 +Inductor_THT:Choke_EPCOS_B82722A +Inductor_THT:Choke_Schaffner_RN102-04-14.0x14.0mm +Inductor_THT:Choke_Schaffner_RN112-04-17.7x17.1mm +Inductor_THT:Choke_Schaffner_RN114-04-22.5x21.5mm +Inductor_THT:Choke_Schaffner_RN116-04-22.5x21.5mm +Inductor_THT:Choke_Schaffner_RN122-04-28.0x27.0mm +Inductor_THT:Choke_Schaffner_RN142-04-33.1x32.5mm +Inductor_THT:Choke_Schaffner_RN143-04-33.1x32.5mm +Inductor_THT:Choke_Schaffner_RN152-04-43.0x41.8mm +Inductor_THT:Choke_Schaffner_RN202-04-8.8x18.2mm +Inductor_THT:Choke_Schaffner_RN204-04-9.0x14.0mm +Inductor_THT:Choke_Schaffner_RN212-04-12.5x18.0mm +Inductor_THT:Choke_Schaffner_RN214-04-15.5x23.0mm +Inductor_THT:Choke_Schaffner_RN216-04-15.5x23.0mm +Inductor_THT:Choke_Schaffner_RN218-04-12.5x18.0mm +Inductor_THT:Choke_Schaffner_RN222-04-18.0x31.0mm +Inductor_THT:Choke_Schaffner_RN232-04-18.0x31.0mm +Inductor_THT:Choke_Schaffner_RN242-04-18.0x31.0mm +Inductor_THT:L_Axial_L11.0mm_D4.5mm_P15.24mm_Horizontal_Fastron_MECC +Inductor_THT:L_Axial_L11.0mm_D4.5mm_P5.08mm_Vertical_Fastron_MECC +Inductor_THT:L_Axial_L11.0mm_D4.5mm_P7.62mm_Vertical_Fastron_MECC +Inductor_THT:L_Axial_L12.0mm_D5.0mm_P15.24mm_Horizontal_Fastron_MISC +Inductor_THT:L_Axial_L12.0mm_D5.0mm_P5.08mm_Vertical_Fastron_MISC +Inductor_THT:L_Axial_L12.0mm_D5.0mm_P7.62mm_Vertical_Fastron_MISC +Inductor_THT:L_Axial_L12.8mm_D5.8mm_P20.32mm_Horizontal_Fastron_HBCC +Inductor_THT:L_Axial_L12.8mm_D5.8mm_P25.40mm_Horizontal_Fastron_HBCC +Inductor_THT:L_Axial_L12.8mm_D5.8mm_P5.08mm_Vertical_Fastron_HBCC +Inductor_THT:L_Axial_L12.8mm_D5.8mm_P7.62mm_Vertical_Fastron_HBCC +Inductor_THT:L_Axial_L13.0mm_D4.5mm_P15.24mm_Horizontal_Fastron_HCCC +Inductor_THT:L_Axial_L13.0mm_D4.5mm_P5.08mm_Vertical_Fastron_HCCC +Inductor_THT:L_Axial_L13.0mm_D4.5mm_P7.62mm_Vertical_Fastron_HCCC +Inductor_THT:L_Axial_L14.0mm_D4.5mm_P15.24mm_Horizontal_Fastron_LACC +Inductor_THT:L_Axial_L14.0mm_D4.5mm_P5.08mm_Vertical_Fastron_LACC +Inductor_THT:L_Axial_L14.0mm_D4.5mm_P7.62mm_Vertical_Fastron_LACC +Inductor_THT:L_Axial_L14.5mm_D5.8mm_P20.32mm_Horizontal_Fastron_HBCC +Inductor_THT:L_Axial_L14.5mm_D5.8mm_P25.40mm_Horizontal_Fastron_HBCC +Inductor_THT:L_Axial_L14.5mm_D5.8mm_P5.08mm_Vertical_Fastron_HBCC +Inductor_THT:L_Axial_L14.5mm_D5.8mm_P7.62mm_Vertical_Fastron_HBCC +Inductor_THT:L_Axial_L16.0mm_D6.3mm_P20.32mm_Horizontal_Fastron_VHBCC +Inductor_THT:L_Axial_L16.0mm_D6.3mm_P25.40mm_Horizontal_Fastron_VHBCC +Inductor_THT:L_Axial_L16.0mm_D6.3mm_P5.08mm_Vertical_Fastron_VHBCC +Inductor_THT:L_Axial_L16.0mm_D6.3mm_P7.62mm_Vertical_Fastron_VHBCC +Inductor_THT:L_Axial_L16.0mm_D7.5mm_P20.32mm_Horizontal_Fastron_XHBCC +Inductor_THT:L_Axial_L16.0mm_D7.5mm_P25.40mm_Horizontal_Fastron_XHBCC +Inductor_THT:L_Axial_L16.0mm_D7.5mm_P5.08mm_Vertical_Fastron_XHBCC +Inductor_THT:L_Axial_L16.0mm_D7.5mm_P7.62mm_Vertical_Fastron_XHBCC +Inductor_THT:L_Axial_L16.0mm_D9.5mm_P20.32mm_Horizontal_Vishay_IM-10-37 +Inductor_THT:L_Axial_L16.0mm_D9.5mm_P5.08mm_Vertical_Vishay_IM-10-37 +Inductor_THT:L_Axial_L17.5mm_D12.0mm_P20.32mm_Horizontal_Vishay_IM-10-46 +Inductor_THT:L_Axial_L17.5mm_D12.0mm_P7.62mm_Vertical_Vishay_IM-10-46 +Inductor_THT:L_Axial_L20.0mm_D8.0mm_P25.40mm_Horizontal +Inductor_THT:L_Axial_L20.0mm_D8.0mm_P5.08mm_Vertical +Inductor_THT:L_Axial_L20.0mm_D8.0mm_P7.62mm_Vertical +Inductor_THT:L_Axial_L20.3mm_D12.1mm_P28.50mm_Horizontal_Vishay_IHA-101 +Inductor_THT:L_Axial_L20.3mm_D12.1mm_P7.62mm_Vertical_Vishay_IHA-101 +Inductor_THT:L_Axial_L20.3mm_D12.7mm_P25.40mm_Horizontal_Vishay_IHA-201 +Inductor_THT:L_Axial_L20.3mm_D12.7mm_P7.62mm_Vertical_Vishay_IHA-201 +Inductor_THT:L_Axial_L23.4mm_D12.7mm_P32.00mm_Horizontal_Vishay_IHA-203 +Inductor_THT:L_Axial_L23.4mm_D12.7mm_P7.62mm_Vertical_Vishay_IHA-203 +Inductor_THT:L_Axial_L24.0mm_D7.1mm_P30.48mm_Horizontal_Vishay_IM-10-28 +Inductor_THT:L_Axial_L24.0mm_D7.1mm_P5.08mm_Vertical_Vishay_IM-10-28 +Inductor_THT:L_Axial_L24.0mm_D7.5mm_P27.94mm_Horizontal_Fastron_MESC +Inductor_THT:L_Axial_L24.0mm_D7.5mm_P5.08mm_Vertical_Fastron_MESC +Inductor_THT:L_Axial_L24.0mm_D7.5mm_P7.62mm_Vertical_Fastron_MESC +Inductor_THT:L_Axial_L26.0mm_D10.0mm_P30.48mm_Horizontal_Fastron_77A +Inductor_THT:L_Axial_L26.0mm_D10.0mm_P5.08mm_Vertical_Fastron_77A +Inductor_THT:L_Axial_L26.0mm_D10.0mm_P7.62mm_Vertical_Fastron_77A +Inductor_THT:L_Axial_L26.0mm_D11.0mm_P30.48mm_Horizontal_Fastron_77A +Inductor_THT:L_Axial_L26.0mm_D11.0mm_P5.08mm_Vertical_Fastron_77A +Inductor_THT:L_Axial_L26.0mm_D11.0mm_P7.62mm_Vertical_Fastron_77A +Inductor_THT:L_Axial_L26.0mm_D9.0mm_P30.48mm_Horizontal_Fastron_77A +Inductor_THT:L_Axial_L26.0mm_D9.0mm_P5.08mm_Vertical_Fastron_77A +Inductor_THT:L_Axial_L26.0mm_D9.0mm_P7.62mm_Vertical_Fastron_77A +Inductor_THT:L_Axial_L26.7mm_D12.1mm_P35.00mm_Horizontal_Vishay_IHA-103 +Inductor_THT:L_Axial_L26.7mm_D12.1mm_P7.62mm_Vertical_Vishay_IHA-103 +Inductor_THT:L_Axial_L26.7mm_D14.0mm_P35.00mm_Horizontal_Vishay_IHA-104 +Inductor_THT:L_Axial_L26.7mm_D14.0mm_P7.62mm_Vertical_Vishay_IHA-104 +Inductor_THT:L_Axial_L29.9mm_D14.0mm_P38.00mm_Horizontal_Vishay_IHA-105 +Inductor_THT:L_Axial_L29.9mm_D14.0mm_P7.62mm_Vertical_Vishay_IHA-105 +Inductor_THT:L_Axial_L30.0mm_D8.0mm_P35.56mm_Horizontal_Fastron_77A +Inductor_THT:L_Axial_L30.0mm_D8.0mm_P5.08mm_Vertical_Fastron_77A +Inductor_THT:L_Axial_L30.0mm_D8.0mm_P7.62mm_Vertical_Fastron_77A +Inductor_THT:L_Axial_L5.0mm_D3.6mm_P10.00mm_Horizontal_Murata_BL01RN1A2A2 +Inductor_THT:L_Axial_L5.3mm_D2.2mm_P10.16mm_Horizontal_Vishay_IM-1 +Inductor_THT:L_Axial_L5.3mm_D2.2mm_P2.54mm_Vertical_Vishay_IM-1 +Inductor_THT:L_Axial_L5.3mm_D2.2mm_P7.62mm_Horizontal_Vishay_IM-1 +Inductor_THT:L_Axial_L6.6mm_D2.7mm_P10.16mm_Horizontal_Vishay_IM-2 +Inductor_THT:L_Axial_L6.6mm_D2.7mm_P2.54mm_Vertical_Vishay_IM-2 +Inductor_THT:L_Axial_L7.0mm_D3.3mm_P10.16mm_Horizontal_Fastron_MICC +Inductor_THT:L_Axial_L7.0mm_D3.3mm_P12.70mm_Horizontal_Fastron_MICC +Inductor_THT:L_Axial_L7.0mm_D3.3mm_P2.54mm_Vertical_Fastron_MICC +Inductor_THT:L_Axial_L7.0mm_D3.3mm_P5.08mm_Vertical_Fastron_MICC +Inductor_THT:L_Axial_L9.5mm_D4.0mm_P12.70mm_Horizontal_Fastron_SMCC +Inductor_THT:L_Axial_L9.5mm_D4.0mm_P15.24mm_Horizontal_Fastron_SMCC +Inductor_THT:L_Axial_L9.5mm_D4.0mm_P2.54mm_Vertical_Fastron_SMCC +Inductor_THT:L_Axial_L9.5mm_D4.0mm_P5.08mm_Vertical_Fastron_SMCC +Inductor_THT:L_CommonMode_PulseElectronics_PH9455x105NL_1 +Inductor_THT:L_CommonMode_PulseElectronics_PH9455x155NL_1 +Inductor_THT:L_CommonMode_PulseElectronics_PH9455x205NL_1 +Inductor_THT:L_CommonMode_PulseElectronics_PH9455x405NL_1 +Inductor_THT:L_CommonMode_PulseElectronics_PH9455x705NL_1 +Inductor_THT:L_CommonMode_PulseElectronics_PH9455xxx6NL_2 +Inductor_THT:L_CommonMode_TDK_B82746S4143A040 +Inductor_THT:L_CommonMode_TDK_B82746S6702A040 +Inductor_THT:L_CommonMode_TDK_B82747E6163A040 +Inductor_THT:L_CommonMode_TDK_B82747E6203A040 +Inductor_THT:L_CommonMode_TDK_B82747E6253A040 +Inductor_THT:L_CommonMode_TDK_B82747E6353A040 +Inductor_THT:L_CommonMode_TDK_B82767S4123N030 +Inductor_THT:L_CommonMode_TDK_B82767S4193N030 +Inductor_THT:L_CommonMode_TDK_B82767S4263N030 +Inductor_THT:L_CommonMode_Toroid_Vertical_L19.3mm_W10.8mm_Px6.35mm_Py15.24mm_Bourns_8100 +Inductor_THT:L_CommonMode_Toroid_Vertical_L21.0mm_W10.0mm_Px5.08mm_Py12.70mm_Murata_5100 +Inductor_THT:L_CommonMode_Toroid_Vertical_L24.0mm_W16.3mm_Px10.16mm_Py20.32mm_Murata_5200 +Inductor_THT:L_CommonMode_Toroid_Vertical_L30.5mm_W15.2mm_Px10.16mm_Py20.32mm_Bourns_8100 +Inductor_THT:L_CommonMode_Toroid_Vertical_L34.3mm_W20.3mm_Px15.24mm_Py22.86mm_Bourns_8100 +Inductor_THT:L_CommonMode_Toroid_Vertical_L36.8mm_W20.3mm_Px15.24mm_Py22.86mm_Bourns_8100 +Inductor_THT:L_CommonMode_Toroid_Vertical_L38.1mm_W20.3mm_Px15.24mm_Py22.86mm_Bourns_8100 +Inductor_THT:L_CommonMode_Toroid_Vertical_L39.4mm_W20.3mm_Px15.24mm_Py22.86mm_Bourns_8100 +Inductor_THT:L_CommonMode_Toroid_Vertical_L41.9mm_W20.3mm_Px15.24mm_Py22.86mm_Bourns_8100 +Inductor_THT:L_CommonMode_Toroid_Vertical_L43.2mm_W22.9mm_Px17.78mm_Py30.48mm_Bourns_8100 +Inductor_THT:L_CommonMode_VAC_T60405-S6123-X140 +Inductor_THT:L_CommonMode_VAC_T60405-S6123-X240 +Inductor_THT:L_CommonMode_VAC_T60405-S6123-X402 +Inductor_THT:L_CommonMode_Wuerth_WE-CMB-L +Inductor_THT:L_CommonMode_Wuerth_WE-CMB-M +Inductor_THT:L_CommonMode_Wuerth_WE-CMB-S +Inductor_THT:L_CommonMode_Wuerth_WE-CMB-XL +Inductor_THT:L_CommonMode_Wuerth_WE-CMB-XS +Inductor_THT:L_CommonMode_Wuerth_WE-CMB-XXL +Inductor_THT:L_Mount_Lodestone_VTM120 +Inductor_THT:L_Mount_Lodestone_VTM160 +Inductor_THT:L_Mount_Lodestone_VTM254 +Inductor_THT:L_Mount_Lodestone_VTM280 +Inductor_THT:L_Mount_Lodestone_VTM950-6 +Inductor_THT:L_Radial_D10.0mm_P5.00mm_Fastron_07M +Inductor_THT:L_Radial_D10.0mm_P5.00mm_Fastron_07P +Inductor_THT:L_Radial_D10.0mm_P5.00mm_Neosid_SD12k_style3 +Inductor_THT:L_Radial_D10.0mm_P5.00mm_Neosid_SD12_style3 +Inductor_THT:L_Radial_D10.5mm_P4.00x5.00mm_Murata_1200RS +Inductor_THT:L_Radial_D10.5mm_P5.00mm_Abacron_AISR-01 +Inductor_THT:L_Radial_D12.0mm_P10.00mm_Neosid_SD12k_style1 +Inductor_THT:L_Radial_D12.0mm_P10.00mm_Neosid_SD12_style1 +Inductor_THT:L_Radial_D12.0mm_P5.00mm_Fastron_11P +Inductor_THT:L_Radial_D12.0mm_P5.00mm_Neosid_SD12k_style2 +Inductor_THT:L_Radial_D12.0mm_P5.00mm_Neosid_SD12_style2 +Inductor_THT:L_Radial_D12.0mm_P6.00mm_Murata_1900R +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 +Inductor_THT:L_Radial_D18.0mm_P10.00mm +Inductor_THT:L_Radial_D21.0mm_P14.61mm_Vishay_IHB-2 +Inductor_THT:L_Radial_D21.0mm_P15.00mm_Vishay_IHB-2 +Inductor_THT:L_Radial_D21.0mm_P15.24mm_Vishay_IHB-2 +Inductor_THT:L_Radial_D21.0mm_P15.75mm_Vishay_IHB-2 +Inductor_THT:L_Radial_D21.0mm_P19.00mm +Inductor_THT:L_Radial_D24.0mm_P24.00mm +Inductor_THT:L_Radial_D24.4mm_P22.90mm_Murata_1400series +Inductor_THT:L_Radial_D24.4mm_P23.10mm_Murata_1400series +Inductor_THT:L_Radial_D24.4mm_P23.40mm_Murata_1400series +Inductor_THT:L_Radial_D24.4mm_P23.70mm_Murata_1400series +Inductor_THT:L_Radial_D24.4mm_P23.90mm_Murata_1400series +Inductor_THT:L_Radial_D27.9mm_P18.29mm_Vishay_IHB-3 +Inductor_THT:L_Radial_D27.9mm_P19.05mm_Vishay_IHB-3 +Inductor_THT:L_Radial_D27.9mm_P20.07mm_Vishay_IHB-3 +Inductor_THT:L_Radial_D28.0mm_P29.20mm +Inductor_THT:L_Radial_D29.8mm_P28.30mm_Murata_1400series +Inductor_THT:L_Radial_D29.8mm_P28.50mm_Murata_1400series +Inductor_THT:L_Radial_D29.8mm_P28.80mm_Murata_1400series +Inductor_THT:L_Radial_D29.8mm_P29.00mm_Murata_1400series +Inductor_THT:L_Radial_D29.8mm_P29.30mm_Murata_1400series +Inductor_THT:L_Radial_D40.6mm_P26.16mm_Vishay_IHB-5 +Inductor_THT:L_Radial_D40.6mm_P27.18mm_Vishay_IHB-4 +Inductor_THT:L_Radial_D40.6mm_P27.94mm_Vishay_IHB-4 +Inductor_THT:L_Radial_D40.6mm_P27.94mm_Vishay_IHB-5 +Inductor_THT:L_Radial_D40.6mm_P28.70mm_Vishay_IHB-5 +Inductor_THT:L_Radial_D50.8mm_P33.27mm_Vishay_IHB-6 +Inductor_THT:L_Radial_D50.8mm_P34.29mm_Vishay_IHB-6 +Inductor_THT:L_Radial_D50.8mm_P35.81mm_Vishay_IHB-6 +Inductor_THT:L_Radial_D50.8mm_P36.32mm_Vishay_IHB-6 +Inductor_THT:L_Radial_D50.8mm_P38.86mm_Vishay_IHB-6 +Inductor_THT:L_Radial_D6.0mm_P4.00mm +Inductor_THT:L_Radial_D7.0mm_P3.00mm +Inductor_THT:L_Radial_D7.2mm_P3.00mm_Murata_1700 +Inductor_THT:L_Radial_D7.5mm_P3.50mm_Fastron_07P +Inductor_THT:L_Radial_D7.5mm_P5.00mm_Fastron_07P +Inductor_THT:L_Radial_D7.8mm_P5.00mm_Fastron_07HCP +Inductor_THT:L_Radial_D8.7mm_P5.00mm_Fastron_07HCP +Inductor_THT:L_Radial_D9.5mm_P5.00mm_Fastron_07HVP +Inductor_THT:L_Radial_L10.2mm_W10.2mm_Px7.62mm_Py7.62mm_Pulse_LP-30 +Inductor_THT:L_Radial_L11.5mm_W11.5mm_Px6.00mm_Py6.00mm_Neosid_NE-CPB-11EN_Drill1.3mm +Inductor_THT:L_Radial_L11.5mm_W11.5mm_Px6.00mm_Py6.00mm_Neosid_NE-CPB-11EN_Drill1.5mm +Inductor_THT:L_Radial_L11.5mm_W11.5mm_Px6.00mm_Py6.00mm_Neosid_NE-CPB-11EN_Drill1.7mm +Inductor_THT:L_Radial_L11.5mm_W11.5mm_Px6.00mm_Py6.00mm_Neosid_NE-CPB-11EN_Drill1.8mm +Inductor_THT:L_Radial_L12.6mm_W12.6mm_Px9.52mm_Py9.52mm_Pulse_LP-37 +Inductor_THT:L_Radial_L16.1mm_W16.1mm_Px7.62mm_Py12.70mm_Pulse_LP-44 +Inductor_THT:L_Radial_L7.5mm_W4.6mm_P5.00mm_Neosid_SD75 +Inductor_THT:L_Radial_L8.0mm_W8.0mm_P5.00mm_Neosid_NE-CPB-07E +Inductor_THT:L_Radial_L8.0mm_W8.0mm_P5.00mm_Neosid_SD8 +Inductor_THT:L_Radial_L9.1mm_W9.1mm_Px6.35mm_Py6.35mm_Pulse_LP-25 +Inductor_THT:L_SELF1408 +Inductor_THT:L_SELF1418 +Inductor_THT:L_Toroid_Horizontal_D11.2mm_P17.00mm_Diameter12-5mm_Amidon-T44 +Inductor_THT:L_Toroid_Horizontal_D12.7mm_P20.00mm_Diameter14-5mm_Amidon-T50 +Inductor_THT:L_Toroid_Horizontal_D16.8mm_P14.70mm_Vishay_TJ3 +Inductor_THT:L_Toroid_Horizontal_D16.8mm_P14.70mm_Vishay_TJ3_BigPads +Inductor_THT:L_Toroid_Horizontal_D17.3mm_P15.24mm_Bourns_2000 +Inductor_THT:L_Toroid_Horizontal_D21.8mm_P19.10mm_Bourns_2100 +Inductor_THT:L_Toroid_Horizontal_D21.8mm_P19.60mm_Bourns_2100 +Inductor_THT:L_Toroid_Horizontal_D22.4mm_P19.80mm_Vishay_TJ4 +Inductor_THT:L_Toroid_Horizontal_D24.1mm_P21.80mm_Bourns_2200 +Inductor_THT:L_Toroid_Horizontal_D24.1mm_P23.10mm_Bourns_2200 +Inductor_THT:L_Toroid_Horizontal_D25.4mm_P22.90mm_Vishay_TJ5 +Inductor_THT:L_Toroid_Horizontal_D25.4mm_P22.90mm_Vishay_TJ5_BigPads +Inductor_THT:L_Toroid_Horizontal_D26.0mm_P5.08mm +Inductor_THT:L_Toroid_Horizontal_D28.0mm_P25.10mm_Bourns_2200 +Inductor_THT:L_Toroid_Horizontal_D28.0mm_P26.67mm_Bourns_2200 +Inductor_THT:L_Toroid_Horizontal_D3.2mm_P6.40mm_Diameter3-5mm_Amidon-T12 +Inductor_THT:L_Toroid_Horizontal_D32.5mm_P28.90mm_Bourns_2300 +Inductor_THT:L_Toroid_Horizontal_D32.5mm_P30.00mm_Bourns_2300 +Inductor_THT:L_Toroid_Horizontal_D35.1mm_P31.00mm_Vishay_TJ6 +Inductor_THT:L_Toroid_Horizontal_D4.1mm_P8.00mm_Diameter4-5mm_Amidon-T16 +Inductor_THT:L_Toroid_Horizontal_D40.0mm_P48.26mm +Inductor_THT:L_Toroid_Horizontal_D41.9mm_P37.60mm_Vishay_TJ7 +Inductor_THT:L_Toroid_Horizontal_D49.3mm_P44.60mm_Vishay_TJ8 +Inductor_THT:L_Toroid_Horizontal_D5.1mm_P9.00mm_Diameter6-5mm_Amidon-T20 +Inductor_THT:L_Toroid_Horizontal_D6.5mm_P10.00mm_Diameter7-5mm_Amidon-T25 +Inductor_THT:L_Toroid_Horizontal_D69.1mm_P63.20mm_Vishay_TJ9 +Inductor_THT:L_Toroid_Horizontal_D7.8mm_P13.00mm_Diameter9-5mm_Amidon-T30 +Inductor_THT:L_Toroid_Horizontal_D9.5mm_P15.00mm_Diameter10-5mm_Amidon-T37 +Inductor_THT:L_Toroid_Vertical_L10.0mm_W5.0mm_P5.08mm +Inductor_THT:L_Toroid_Vertical_L13.0mm_W6.5mm_P5.60mm +Inductor_THT:L_Toroid_Vertical_L14.0mm_W5.6mm_P5.30mm_Bourns_5700 +Inductor_THT:L_Toroid_Vertical_L14.0mm_W6.3mm_P4.57mm_Pulse_A +Inductor_THT:L_Toroid_Vertical_L14.7mm_W8.6mm_P5.58mm_Pulse_KM-1 +Inductor_THT:L_Toroid_Vertical_L16.0mm_W8.0mm_P7.62mm +Inductor_THT:L_Toroid_Vertical_L16.3mm_W7.1mm_P7.11mm_Pulse_H +Inductor_THT:L_Toroid_Vertical_L16.4mm_W7.6mm_P6.60mm_Vishay_TJ3 +Inductor_THT:L_Toroid_Vertical_L16.5mm_W11.4mm_P7.62mm_Pulse_KM-2 +Inductor_THT:L_Toroid_Vertical_L16.8mm_W9.2mm_P7.10mm_Vishay_TJ3 +Inductor_THT:L_Toroid_Vertical_L16.8mm_W9.2mm_P7.10mm_Vishay_TJ3_BigPads +Inductor_THT:L_Toroid_Vertical_L17.8mm_W8.1mm_P7.62mm_Bourns_5700 +Inductor_THT:L_Toroid_Vertical_L17.8mm_W9.7mm_P7.11mm_Pulse_B +Inductor_THT:L_Toroid_Vertical_L19.1mm_W8.1mm_P7.10mm_Bourns_5700 +Inductor_THT:L_Toroid_Vertical_L21.6mm_W11.4mm_P7.62mm_Pulse_KM-3 +Inductor_THT:L_Toroid_Vertical_L21.6mm_W8.4mm_P8.38mm_Pulse_G +Inductor_THT:L_Toroid_Vertical_L21.6mm_W9.1mm_P8.40mm_Bourns_5700 +Inductor_THT:L_Toroid_Vertical_L21.6mm_W9.5mm_P7.11mm_Pulse_C +Inductor_THT:L_Toroid_Vertical_L22.4mm_W10.2mm_P7.90mm_Vishay_TJ4 +Inductor_THT:L_Toroid_Vertical_L24.6mm_W15.5mm_P11.44mm_Pulse_KM-4 +Inductor_THT:L_Toroid_Vertical_L25.4mm_W14.7mm_P12.20mm_Vishay_TJ5 +Inductor_THT:L_Toroid_Vertical_L25.4mm_W14.7mm_P12.20mm_Vishay_TJ5_BigPads +Inductor_THT:L_Toroid_Vertical_L26.7mm_W14.0mm_P10.16mm_Pulse_D +Inductor_THT:L_Toroid_Vertical_L28.6mm_W14.3mm_P11.43mm_Bourns_5700 +Inductor_THT:L_Toroid_Vertical_L31.8mm_W15.9mm_P13.50mm_Bourns_5700 +Inductor_THT:L_Toroid_Vertical_L33.0mm_W17.8mm_P12.70mm_Pulse_KM-5 +Inductor_THT:L_Toroid_Vertical_L35.1mm_W21.1mm_P18.50mm_Vishay_TJ6 +Inductor_THT:L_Toroid_Vertical_L35.6mm_W17.8mm_P12.70mm_Pulse_E +Inductor_THT:L_Toroid_Vertical_L41.9mm_W17.8mm_P12.70mm_Pulse_F +Inductor_THT:L_Toroid_Vertical_L41.9mm_W19.1mm_P15.80mm_Vishay_TJ7 +Inductor_THT:L_Toroid_Vertical_L46.0mm_W19.1mm_P21.80mm_Bourns_5700 +Inductor_THT:L_Toroid_Vertical_L48.8mm_W25.4mm_P20.80mm_Vishay_TJ8 +Inductor_THT:L_Toroid_Vertical_L54.0mm_W23.8mm_P20.10mm_Bourns_5700 +Inductor_THT:L_Toroid_Vertical_L67.6mm_W36.1mm_P31.80mm_Vishay_TJ9 +Inductor_THT_Wurth:L_Wurth_WE-HCFT-2012_LeadDiameter1.2mm +Inductor_THT_Wurth:L_Wurth_WE-HCFT-2012_LeadDiameter1.5mm +Inductor_THT_Wurth:L_Wurth_WE-HCFT-2504 +Inductor_THT_Wurth:L_Wurth_WE-HCFT-3521 +Inductor_THT_Wurth:L_Wurth_WE-HCFT-3533_LeadDiameter1.8mm +Inductor_THT_Wurth:L_Wurth_WE-HCFT-3533_LeadDiameter2.0mm +Inductor_THT_Wurth:L_Wurth_WE-HCFT-3540_LeadDiameter0.8mm +Inductor_THT_Wurth:L_Wurth_WE-HCFT-3540_LeadDiameter1.3mm +Inductor_THT_Wurth:L_Wurth_WE-HCFT-3540_LeadDiameter1.5mm +Inductor_THT_Wurth:L_Wurth_WE-HCFT-3540_LeadDiameter2.0mm +Jumper:SolderJumper-2_P1.3mm_Bridged2Bar_Pad1.0x1.5mm +Jumper:SolderJumper-2_P1.3mm_Bridged2Bar_RoundedPad1.0x1.5mm +Jumper:SolderJumper-2_P1.3mm_Bridged_Pad1.0x1.5mm +Jumper:SolderJumper-2_P1.3mm_Bridged_RoundedPad1.0x1.5mm +Jumper:SolderJumper-2_P1.3mm_Open_Pad1.0x1.5mm +Jumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm +Jumper:SolderJumper-2_P1.3mm_Open_TrianglePad1.0x1.5mm +Jumper:SolderJumper-3_P1.3mm_Bridged12_Pad1.0x1.5mm +Jumper:SolderJumper-3_P1.3mm_Bridged12_Pad1.0x1.5mm_NumberLabels +Jumper:SolderJumper-3_P1.3mm_Bridged12_RoundedPad1.0x1.5mm +Jumper:SolderJumper-3_P1.3mm_Bridged12_RoundedPad1.0x1.5mm_NumberLabels +Jumper:SolderJumper-3_P1.3mm_Bridged2Bar12_Pad1.0x1.5mm +Jumper:SolderJumper-3_P1.3mm_Bridged2Bar12_Pad1.0x1.5mm_NumberLabels +Jumper:SolderJumper-3_P1.3mm_Bridged2Bar12_RoundedPad1.0x1.5mm +Jumper:SolderJumper-3_P1.3mm_Bridged2Bar12_RoundedPad1.0x1.5mm_NumberLabels +Jumper:SolderJumper-3_P1.3mm_Open_Pad1.0x1.5mm +Jumper:SolderJumper-3_P1.3mm_Open_Pad1.0x1.5mm_NumberLabels +Jumper:SolderJumper-3_P1.3mm_Open_RoundedPad1.0x1.5mm +Jumper:SolderJumper-3_P1.3mm_Open_RoundedPad1.0x1.5mm_NumberLabels +Jumper:SolderJumper-3_P2.0mm_Open_TrianglePad1.0x1.5mm +Jumper:SolderJumper-3_P2.0mm_Open_TrianglePad1.0x1.5mm_NumberLabels +LED_SMD:LED-APA102-2020 +LED_SMD:LED-L1T2_LUMILEDS +LED_SMD:LED_0201_0603Metric +LED_SMD:LED_0201_0603Metric_Pad0.64x0.40mm_HandSolder +LED_SMD:LED_0402_1005Metric +LED_SMD:LED_0402_1005Metric_Pad0.77x0.64mm_HandSolder +LED_SMD:LED_0603_1608Metric +LED_SMD:LED_0603_1608Metric_Pad1.05x0.95mm_HandSolder +LED_SMD:LED_0805_2012Metric +LED_SMD:LED_0805_2012Metric_Pad1.15x1.40mm_HandSolder +LED_SMD:LED_1206_3216Metric +LED_SMD:LED_1206_3216Metric_Pad1.42x1.75mm_HandSolder +LED_SMD:LED_1206_3216Metric_ReverseMount_Hole1.8x2.4mm +LED_SMD:LED_1210_3225Metric +LED_SMD:LED_1210_3225Metric_Pad1.42x2.65mm_HandSolder +LED_SMD:LED_1812_4532Metric +LED_SMD:LED_1812_4532Metric_Pad1.30x3.40mm_HandSolder +LED_SMD:LED_1W_3W_R8 +LED_SMD:LED_2010_5025Metric +LED_SMD:LED_2010_5025Metric_Pad1.52x2.65mm_HandSolder +LED_SMD:LED_2512_6332Metric +LED_SMD:LED_2512_6332Metric_Pad1.52x3.35mm_HandSolder +LED_SMD:LED_ASMB-KTF0-0A306 +LED_SMD:LED_Avago_PLCC4_3.2x2.8mm_CW +LED_SMD:LED_Avago_PLCC6_3x2.8mm +LED_SMD:LED_Cree-PLCC4_2x2mm_CW +LED_SMD:LED_Cree-PLCC4_3.2x2.8mm_CCW +LED_SMD:LED_Cree-PLCC4_5x5mm_CW +LED_SMD:LED_Cree-PLCC6_4.7x1.5mm +LED_SMD:LED_Cree-XB +LED_SMD:LED_Cree-XH +LED_SMD:LED_Cree-XHP35 +LED_SMD:LED_Cree-XHP50_12V +LED_SMD:LED_Cree-XHP50_6V +LED_SMD:LED_Cree-XHP70_12V +LED_SMD:LED_Cree-XHP70_6V +LED_SMD:LED_Cree-XP-G +LED_SMD:LED_Cree-XP +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 +LED_SMD:LED_LiteOn_LTST-S326 +LED_SMD:LED_Lumex_SML-LX0303SIUPGUSB +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 +LED_SMD:LED_PLCC-2_3x2mm_AK +LED_SMD:LED_PLCC-2_3x2mm_KA +LED_SMD:LED_PLCC_2835 +LED_SMD:LED_PLCC_2835_Handsoldering +LED_SMD:LED_RGB_1210 +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 +LED_SMD:LED_Yuji_5730 +LED_THT:LED_BL-FL7680RGB +LED_THT:LED_D1.8mm_W1.8mm_H2.4mm_Horizontal_O1.27mm_Z1.6mm +LED_THT:LED_D1.8mm_W1.8mm_H2.4mm_Horizontal_O1.27mm_Z4.9mm +LED_THT:LED_D1.8mm_W1.8mm_H2.4mm_Horizontal_O1.27mm_Z8.2mm +LED_THT:LED_D1.8mm_W1.8mm_H2.4mm_Horizontal_O3.81mm_Z1.6mm +LED_THT:LED_D1.8mm_W1.8mm_H2.4mm_Horizontal_O3.81mm_Z4.9mm +LED_THT:LED_D1.8mm_W1.8mm_H2.4mm_Horizontal_O3.81mm_Z8.2mm +LED_THT:LED_D1.8mm_W1.8mm_H2.4mm_Horizontal_O6.35mm_Z1.6mm +LED_THT:LED_D1.8mm_W1.8mm_H2.4mm_Horizontal_O6.35mm_Z4.9mm +LED_THT:LED_D1.8mm_W1.8mm_H2.4mm_Horizontal_O6.35mm_Z8.2mm +LED_THT:LED_D1.8mm_W3.3mm_H2.4mm +LED_THT:LED_D10.0mm-3 +LED_THT:LED_D10.0mm +LED_THT:LED_D2.0mm_W4.0mm_H2.8mm_FlatTop +LED_THT:LED_D2.0mm_W4.8mm_H2.5mm_FlatTop +LED_THT:LED_D20.0mm +LED_THT:LED_D3.0mm-3 +LED_THT:LED_D3.0mm +LED_THT:LED_D3.0mm_Clear +LED_THT:LED_D3.0mm_FlatTop +LED_THT:LED_D3.0mm_Horizontal_O1.27mm_Z10.0mm +LED_THT:LED_D3.0mm_Horizontal_O1.27mm_Z2.0mm +LED_THT:LED_D3.0mm_Horizontal_O1.27mm_Z2.0mm_Clear +LED_THT:LED_D3.0mm_Horizontal_O1.27mm_Z2.0mm_IRBlack +LED_THT:LED_D3.0mm_Horizontal_O1.27mm_Z2.0mm_IRGrey +LED_THT:LED_D3.0mm_Horizontal_O1.27mm_Z6.0mm +LED_THT:LED_D3.0mm_Horizontal_O3.81mm_Z10.0mm +LED_THT:LED_D3.0mm_Horizontal_O3.81mm_Z2.0mm +LED_THT:LED_D3.0mm_Horizontal_O3.81mm_Z6.0mm +LED_THT:LED_D3.0mm_Horizontal_O6.35mm_Z10.0mm +LED_THT:LED_D3.0mm_Horizontal_O6.35mm_Z2.0mm +LED_THT:LED_D3.0mm_Horizontal_O6.35mm_Z6.0mm +LED_THT:LED_D3.0mm_IRBlack +LED_THT:LED_D3.0mm_IRGrey +LED_THT:LED_D4.0mm +LED_THT:LED_D5.0mm-3 +LED_THT:LED_D5.0mm-3_Horizontal_O3.81mm_Z3.0mm +LED_THT:LED_D5.0mm-4_RGB +LED_THT:LED_D5.0mm-4_RGB_Staggered_Pins +LED_THT:LED_D5.0mm-4_RGB_Wide_Pins +LED_THT:LED_D5.0mm +LED_THT:LED_D5.0mm_Clear +LED_THT:LED_D5.0mm_FlatTop +LED_THT:LED_D5.0mm_Horizontal_O1.27mm_Z15.0mm +LED_THT:LED_D5.0mm_Horizontal_O1.27mm_Z3.0mm +LED_THT:LED_D5.0mm_Horizontal_O1.27mm_Z3.0mm_Clear +LED_THT:LED_D5.0mm_Horizontal_O1.27mm_Z3.0mm_IRBlack +LED_THT:LED_D5.0mm_Horizontal_O1.27mm_Z3.0mm_IRGrey +LED_THT:LED_D5.0mm_Horizontal_O1.27mm_Z9.0mm +LED_THT:LED_D5.0mm_Horizontal_O3.81mm_Z15.0mm +LED_THT:LED_D5.0mm_Horizontal_O3.81mm_Z3.0mm +LED_THT:LED_D5.0mm_Horizontal_O3.81mm_Z9.0mm +LED_THT:LED_D5.0mm_Horizontal_O6.35mm_Z15.0mm +LED_THT:LED_D5.0mm_Horizontal_O6.35mm_Z3.0mm +LED_THT:LED_D5.0mm_Horizontal_O6.35mm_Z9.0mm +LED_THT:LED_D5.0mm_IRBlack +LED_THT:LED_D5.0mm_IRGrey +LED_THT:LED_D8.0mm-3 +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.9mm +LED_THT:LED_Rectangular_W5.0mm_H2.0mm-3Pins +LED_THT:LED_Rectangular_W5.0mm_H2.0mm +LED_THT:LED_Rectangular_W5.0mm_H2.0mm_Horizontal_O1.27mm_Z1.0mm +LED_THT:LED_Rectangular_W5.0mm_H2.0mm_Horizontal_O1.27mm_Z3.0mm +LED_THT:LED_Rectangular_W5.0mm_H2.0mm_Horizontal_O1.27mm_Z5.0mm +LED_THT:LED_Rectangular_W5.0mm_H2.0mm_Horizontal_O3.81mm_Z1.0mm +LED_THT:LED_Rectangular_W5.0mm_H2.0mm_Horizontal_O3.81mm_Z3.0mm +LED_THT:LED_Rectangular_W5.0mm_H2.0mm_Horizontal_O3.81mm_Z5.0mm +LED_THT:LED_Rectangular_W5.0mm_H2.0mm_Horizontal_O6.35mm_Z1.0mm +LED_THT:LED_Rectangular_W5.0mm_H2.0mm_Horizontal_O6.35mm_Z3.0mm +LED_THT:LED_Rectangular_W5.0mm_H2.0mm_Horizontal_O6.35mm_Z5.0mm +LED_THT:LED_Rectangular_W5.0mm_H5.0mm +LED_THT:LED_Rectangular_W7.62mm_H4.55mm_P5.08mm_R3 +LED_THT:LED_SideEmitter_Rectangular_W4.5mm_H1.6mm +LED_THT:LED_VCCLite_5381H1_6.35x6.35mm +LED_THT:LED_VCCLite_5381H3_6.35x6.35mm +LED_THT:LED_VCCLite_5381H5_6.35x6.35mm +LED_THT:LED_VCCLite_5381H7_6.35x6.35mm +Module:A20_OLINUXINO_LIME2 +Module:Adafruit_Feather +Module:Adafruit_Feather_32u4_FONA +Module:Adafruit_Feather_32u4_FONA_WithMountingHoles +Module:Adafruit_Feather_32u4_RFM +Module:Adafruit_Feather_32u4_RFM_WithMountingHoles +Module:Adafruit_Feather_M0_RFM +Module:Adafruit_Feather_M0_RFM_WithMountingHoles +Module:Adafruit_Feather_M0_Wifi +Module:Adafruit_Feather_M0_Wifi_WithMountingHoles +Module:Adafruit_Feather_WICED +Module:Adafruit_Feather_WICED_WithMountingHoles +Module:Adafruit_Feather_WithMountingHoles +Module:Adafruit_HUZZAH_ESP8266_breakout +Module:Adafruit_HUZZAH_ESP8266_breakout_WithMountingHoles +Module:Arduino_Nano +Module:Arduino_Nano_WithMountingHoles +Module:Arduino_UNO_R2 +Module:Arduino_UNO_R2_WithMountingHoles +Module:Arduino_UNO_R3 +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 +Motors:Vybronics_VZ30C1T8219732L +MountingEquipment:DINRailAdapter_3xM3_PhoenixContact_1201578 +MountingHole:MountingHole_2.1mm +MountingHole:MountingHole_2.2mm_M2 +MountingHole:MountingHole_2.2mm_M2_DIN965 +MountingHole:MountingHole_2.2mm_M2_DIN965_Pad +MountingHole:MountingHole_2.2mm_M2_DIN965_Pad_TopBottom +MountingHole:MountingHole_2.2mm_M2_DIN965_Pad_TopOnly +MountingHole:MountingHole_2.2mm_M2_ISO14580 +MountingHole:MountingHole_2.2mm_M2_ISO14580_Pad +MountingHole:MountingHole_2.2mm_M2_ISO14580_Pad_TopBottom +MountingHole:MountingHole_2.2mm_M2_ISO14580_Pad_TopOnly +MountingHole:MountingHole_2.2mm_M2_ISO7380 +MountingHole:MountingHole_2.2mm_M2_ISO7380_Pad +MountingHole:MountingHole_2.2mm_M2_ISO7380_Pad_TopBottom +MountingHole:MountingHole_2.2mm_M2_ISO7380_Pad_TopOnly +MountingHole:MountingHole_2.2mm_M2_Pad +MountingHole:MountingHole_2.2mm_M2_Pad_TopBottom +MountingHole:MountingHole_2.2mm_M2_Pad_TopOnly +MountingHole:MountingHole_2.2mm_M2_Pad_Via +MountingHole:MountingHole_2.5mm +MountingHole:MountingHole_2.5mm_Pad +MountingHole:MountingHole_2.5mm_Pad_TopBottom +MountingHole:MountingHole_2.5mm_Pad_TopOnly +MountingHole:MountingHole_2.5mm_Pad_Via +MountingHole:MountingHole_2.7mm +MountingHole:MountingHole_2.7mm_M2.5 +MountingHole:MountingHole_2.7mm_M2.5_DIN965 +MountingHole:MountingHole_2.7mm_M2.5_DIN965_Pad +MountingHole:MountingHole_2.7mm_M2.5_DIN965_Pad_TopBottom +MountingHole:MountingHole_2.7mm_M2.5_DIN965_Pad_TopOnly +MountingHole:MountingHole_2.7mm_M2.5_ISO14580 +MountingHole:MountingHole_2.7mm_M2.5_ISO14580_Pad +MountingHole:MountingHole_2.7mm_M2.5_ISO14580_Pad_TopBottom +MountingHole:MountingHole_2.7mm_M2.5_ISO14580_Pad_TopOnly +MountingHole:MountingHole_2.7mm_M2.5_ISO7380 +MountingHole:MountingHole_2.7mm_M2.5_ISO7380_Pad +MountingHole:MountingHole_2.7mm_M2.5_ISO7380_Pad_TopBottom +MountingHole:MountingHole_2.7mm_M2.5_ISO7380_Pad_TopOnly +MountingHole:MountingHole_2.7mm_M2.5_Pad +MountingHole:MountingHole_2.7mm_M2.5_Pad_TopBottom +MountingHole:MountingHole_2.7mm_M2.5_Pad_TopOnly +MountingHole:MountingHole_2.7mm_M2.5_Pad_Via +MountingHole:MountingHole_2.7mm_Pad +MountingHole:MountingHole_2.7mm_Pad_TopBottom +MountingHole:MountingHole_2.7mm_Pad_TopOnly +MountingHole:MountingHole_2.7mm_Pad_Via +MountingHole:MountingHole_2mm +MountingHole:MountingHole_3.2mm_M3 +MountingHole:MountingHole_3.2mm_M3_DIN965 +MountingHole:MountingHole_3.2mm_M3_DIN965_Pad +MountingHole:MountingHole_3.2mm_M3_DIN965_Pad_TopBottom +MountingHole:MountingHole_3.2mm_M3_DIN965_Pad_TopOnly +MountingHole:MountingHole_3.2mm_M3_ISO14580 +MountingHole:MountingHole_3.2mm_M3_ISO14580_Pad +MountingHole:MountingHole_3.2mm_M3_ISO14580_Pad_TopBottom +MountingHole:MountingHole_3.2mm_M3_ISO14580_Pad_TopOnly +MountingHole:MountingHole_3.2mm_M3_ISO7380 +MountingHole:MountingHole_3.2mm_M3_ISO7380_Pad +MountingHole:MountingHole_3.2mm_M3_ISO7380_Pad_TopBottom +MountingHole:MountingHole_3.2mm_M3_ISO7380_Pad_TopOnly +MountingHole:MountingHole_3.2mm_M3_Pad +MountingHole:MountingHole_3.2mm_M3_Pad_TopBottom +MountingHole:MountingHole_3.2mm_M3_Pad_TopOnly +MountingHole:MountingHole_3.2mm_M3_Pad_Via +MountingHole:MountingHole_3.5mm +MountingHole:MountingHole_3.5mm_Pad +MountingHole:MountingHole_3.5mm_Pad_TopBottom +MountingHole:MountingHole_3.5mm_Pad_TopOnly +MountingHole:MountingHole_3.5mm_Pad_Via +MountingHole:MountingHole_3.7mm +MountingHole:MountingHole_3.7mm_Pad +MountingHole:MountingHole_3.7mm_Pad_TopBottom +MountingHole:MountingHole_3.7mm_Pad_TopOnly +MountingHole:MountingHole_3.7mm_Pad_Via +MountingHole:MountingHole_3mm +MountingHole:MountingHole_3mm_Pad +MountingHole:MountingHole_3mm_Pad_TopBottom +MountingHole:MountingHole_3mm_Pad_TopOnly +MountingHole:MountingHole_3mm_Pad_Via +MountingHole:MountingHole_4.3mm_M4 +MountingHole:MountingHole_4.3mm_M4_DIN965 +MountingHole:MountingHole_4.3mm_M4_DIN965_Pad +MountingHole:MountingHole_4.3mm_M4_DIN965_Pad_TopBottom +MountingHole:MountingHole_4.3mm_M4_DIN965_Pad_TopOnly +MountingHole:MountingHole_4.3mm_M4_ISO14580 +MountingHole:MountingHole_4.3mm_M4_ISO14580_Pad +MountingHole:MountingHole_4.3mm_M4_ISO14580_Pad_TopBottom +MountingHole:MountingHole_4.3mm_M4_ISO14580_Pad_TopOnly +MountingHole:MountingHole_4.3mm_M4_ISO7380 +MountingHole:MountingHole_4.3mm_M4_ISO7380_Pad +MountingHole:MountingHole_4.3mm_M4_ISO7380_Pad_TopBottom +MountingHole:MountingHole_4.3mm_M4_ISO7380_Pad_TopOnly +MountingHole:MountingHole_4.3mm_M4_Pad +MountingHole:MountingHole_4.3mm_M4_Pad_TopBottom +MountingHole:MountingHole_4.3mm_M4_Pad_TopOnly +MountingHole:MountingHole_4.3mm_M4_Pad_Via +MountingHole:MountingHole_4.3x6.2mm_M4_Pad +MountingHole:MountingHole_4.3x6.2mm_M4_Pad_Via +MountingHole:MountingHole_4.5mm +MountingHole:MountingHole_4.5mm_Pad +MountingHole:MountingHole_4.5mm_Pad_TopBottom +MountingHole:MountingHole_4.5mm_Pad_TopOnly +MountingHole:MountingHole_4.5mm_Pad_Via +MountingHole:MountingHole_4mm +MountingHole:MountingHole_4mm_Pad +MountingHole:MountingHole_4mm_Pad_TopBottom +MountingHole:MountingHole_4mm_Pad_TopOnly +MountingHole:MountingHole_4mm_Pad_Via +MountingHole:MountingHole_5.3mm_M5 +MountingHole:MountingHole_5.3mm_M5_DIN965 +MountingHole:MountingHole_5.3mm_M5_DIN965_Pad +MountingHole:MountingHole_5.3mm_M5_DIN965_Pad_TopBottom +MountingHole:MountingHole_5.3mm_M5_DIN965_Pad_TopOnly +MountingHole:MountingHole_5.3mm_M5_ISO14580 +MountingHole:MountingHole_5.3mm_M5_ISO14580_Pad +MountingHole:MountingHole_5.3mm_M5_ISO14580_Pad_TopBottom +MountingHole:MountingHole_5.3mm_M5_ISO14580_Pad_TopOnly +MountingHole:MountingHole_5.3mm_M5_ISO7380 +MountingHole:MountingHole_5.3mm_M5_ISO7380_Pad +MountingHole:MountingHole_5.3mm_M5_ISO7380_Pad_TopBottom +MountingHole:MountingHole_5.3mm_M5_ISO7380_Pad_TopOnly +MountingHole:MountingHole_5.3mm_M5_Pad +MountingHole:MountingHole_5.3mm_M5_Pad_TopBottom +MountingHole:MountingHole_5.3mm_M5_Pad_TopOnly +MountingHole:MountingHole_5.3mm_M5_Pad_Via +MountingHole:MountingHole_5.5mm +MountingHole:MountingHole_5.5mm_Pad +MountingHole:MountingHole_5.5mm_Pad_TopBottom +MountingHole:MountingHole_5.5mm_Pad_TopOnly +MountingHole:MountingHole_5.5mm_Pad_Via +MountingHole:MountingHole_5mm +MountingHole:MountingHole_5mm_Pad +MountingHole:MountingHole_5mm_Pad_TopBottom +MountingHole:MountingHole_5mm_Pad_TopOnly +MountingHole:MountingHole_5mm_Pad_Via +MountingHole:MountingHole_6.4mm_M6 +MountingHole:MountingHole_6.4mm_M6_DIN965 +MountingHole:MountingHole_6.4mm_M6_DIN965_Pad +MountingHole:MountingHole_6.4mm_M6_DIN965_Pad_TopBottom +MountingHole:MountingHole_6.4mm_M6_DIN965_Pad_TopOnly +MountingHole:MountingHole_6.4mm_M6_ISO14580 +MountingHole:MountingHole_6.4mm_M6_ISO14580_Pad +MountingHole:MountingHole_6.4mm_M6_ISO14580_Pad_TopBottom +MountingHole:MountingHole_6.4mm_M6_ISO14580_Pad_TopOnly +MountingHole:MountingHole_6.4mm_M6_ISO7380 +MountingHole:MountingHole_6.4mm_M6_ISO7380_Pad +MountingHole:MountingHole_6.4mm_M6_ISO7380_Pad_TopBottom +MountingHole:MountingHole_6.4mm_M6_ISO7380_Pad_TopOnly +MountingHole:MountingHole_6.4mm_M6_Pad +MountingHole:MountingHole_6.4mm_M6_Pad_TopBottom +MountingHole:MountingHole_6.4mm_M6_Pad_TopOnly +MountingHole:MountingHole_6.4mm_M6_Pad_Via +MountingHole:MountingHole_6.5mm +MountingHole:MountingHole_6.5mm_Pad +MountingHole:MountingHole_6.5mm_Pad_TopBottom +MountingHole:MountingHole_6.5mm_Pad_TopOnly +MountingHole:MountingHole_6.5mm_Pad_Via +MountingHole:MountingHole_6mm +MountingHole:MountingHole_6mm_Pad +MountingHole:MountingHole_6mm_Pad_TopBottom +MountingHole:MountingHole_6mm_Pad_TopOnly +MountingHole:MountingHole_6mm_Pad_Via +MountingHole:MountingHole_8.4mm_M8 +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 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSE-ExternalM3_H13mm_9771130360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSE-ExternalM3_H14mm_9771140360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSE-ExternalM3_H15mm_9771150360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSE-ExternalM3_H5mm_9771050360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSE-ExternalM3_H6mm_9771060360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSE-ExternalM3_H7mm_9771070360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSE-ExternalM3_H8mm_9771080360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSE-ExternalM3_H9mm_9771090360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-4.5mm_H10mm_9774100482 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-4.5mm_H1mm_9774010482 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-4.5mm_H2mm_9774020482 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-4.5mm_H3mm_9774030482 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-4.5mm_H4mm_9774040482 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-4.5mm_H5mm_9774050482 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-4.5mm_H6mm_9774060482 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-4.5mm_H7mm_9774070482 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-4.5mm_H8mm_9774080482 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-4.5mm_H9mm_9774090482 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H1.5mm_9774015633 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H1mm_9774010633 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H2.5mm_9774025633 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H2.5mm_ThreadDepth1.5mm_97730256332 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H2.5mm_ThreadDepth1.5mm_NoNPTH_97730256330 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H2mm_9774020633 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H3.5mm_ThreadDepth2mm_97730356332 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H3.5mm_ThreadDepth2mm_97730356334 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H3.5mm_ThreadDepth2mm_NoNPTH_97730356330 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H3mm_9774030633 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H3mm_ThreadDepth1.8mm_97730306332 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H3mm_ThreadDepth1.8mm_NoNPTH_97730306330 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H4.5mm_ThreadDepth2mm_97730456332 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H4.5mm_ThreadDepth2mm_97730456334 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H4.5mm_ThreadDepth2mm_NoNPTH_97730456330 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H4mm_ThreadDepth2mm_97730406332 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H4mm_ThreadDepth2mm_97730406334 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H4mm_ThreadDepth2mm_NoNPTH_97730406330 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H5mm_ThreadDepth2mm_97730506332 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H5mm_ThreadDepth2mm_97730506334 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H5mm_ThreadDepth2mm_NoNPTH_97730506330 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H6mm_ThreadDepth2mm_97730606332 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H6mm_ThreadDepth2mm_97730606334 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M1.6_H6mm_ThreadDepth2mm_NoNPTH_97730606330 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M2_H1.5mm_9774015243 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M2_H1mm_9774010243 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M2_H2.5mm_9774025243 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M2_H2mm_9774020243 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M2_H3.5mm_9774035243 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M2_H3mm_9774030243 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M2_H4.5mm_9774045243 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M2_H4mm_9774040243 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M2_H5mm_9774050243 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M2_H6mm_9774060243 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M2_H7mm_9774070243 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M2_H8mm_9774080243 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H1.5mm_9774015360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H10mm_9774100360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H11mm_9774110360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H12mm_9774120360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H13mm_9774130360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H14mm_9774140360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H15mm_9774150360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H1mm_9774010360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H2.5mm_9774025360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H2mm_9774020360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H3mm_9774030360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H4mm_9774040360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H5mm_9774050360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H6mm_9774060360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H7mm_9774070360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H8mm_9774080360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSI-M3_H9mm_9774090360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-3.2mm_H10.6mm_ReverseMount_9775106960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-3.2mm_H11.6mm_ReverseMount_9775116960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-3.2mm_H2.6mm_ReverseMount_9775026960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-3.2mm_H3.1mm_ReverseMount_9775031960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-3.2mm_H3.6mm_ReverseMount_9775036960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-3.2mm_H4.1mm_ReverseMount_9775041960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-3.2mm_H4.6mm_ReverseMount_9775046960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-3.2mm_H5.1mm_ReverseMount_9775051960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-3.2mm_H5.6mm_ReverseMount_9775056960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-3.2mm_H6.6mm_ReverseMount_9775066960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-3.2mm_H7.6mm_ReverseMount_9775076960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-3.2mm_H8.6mm_ReverseMount_9775086960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-3.2mm_H9.6mm_ReverseMount_9775096960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-M3_H10.6mm_ReverseMount_9775106360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-M3_H11.6mm_ReverseMount_9775116360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-M3_H2.6mm_ReverseMount_9775026360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-M3_H3.1mm_ReverseMount_9775031360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-M3_H3.6mm_ReverseMount_9775036360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-M3_H4.1mm_ReverseMount_9775041360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-M3_H4.6mm_ReverseMount_9775046360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-M3_H5.1mm_ReverseMount_9775051360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-M3_H5.6mm_ReverseMount_9775056360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-M3_H6.6mm_ReverseMount_9775066360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-M3_H7.6mm_ReverseMount_9775076360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-M3_H8.6mm_ReverseMount_9775086360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSR-M3_H9.6mm_ReverseMount_9775096360 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSSR-3.3mm_H10mm_SnapRivet_9776100960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSSR-3.3mm_H2.5mm_SnapRivet_9776025960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSSR-3.3mm_H2mm_SnapRivet_9776020960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSSR-3.3mm_H3mm_SnapRivet_9776030960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSSR-3.3mm_H4mm_SnapRivet_9776040960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSSR-3.3mm_H5mm_SnapRivet_9776050960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSSR-3.3mm_H6mm_SnapRivet_9776060960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSSR-3.3mm_H7mm_SnapRivet_9776070960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSSR-3.3mm_H8mm_SnapRivet_9776080960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMSSR-3.3mm_H9mm_SnapRivet_9776090960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.25mm_H1.5mm_9774015943 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.25mm_H1mm_9774010943 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.25mm_H2.5mm_9774025943 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.25mm_H2mm_9774020943 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.25mm_H3.5mm_9774035943 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.25mm_H3mm_9774030943 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.25mm_H4.5mm_9774045943 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.25mm_H4mm_9774040943 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.25mm_H5mm_9774050943 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.25mm_H6mm_9774060943 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.25mm_H7mm_9774070943 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.25mm_H8mm_9774080943 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.7mm_H1.5mm_9774015951 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.7mm_H10mm_9774100951 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.7mm_H1mm_9774010951 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.7mm_H2.5mm_9774025951 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.7mm_H2mm_9774020951 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.7mm_H3mm_9774030951 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.7mm_H4mm_9774040951 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.7mm_H5.5mm_9774055951 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.7mm_H5mm_9774050951 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.7mm_H6.5mm_9774065951 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.7mm_H6mm_9774060951 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.7mm_H7mm_9774070951 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.7mm_H8mm_9774080951 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-2.7mm_H9mm_9774090951 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H1.5mm_9774015960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H10mm_9774100960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H11mm_9774110960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H12mm_9774120960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H13mm_9774130960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H14mm_9774140960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H15mm_9774150960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H1mm_9774010960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H2.5mm_9774025960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H2mm_9774020960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H3mm_9774030960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H4mm_9774040960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H5mm_9774050960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H6mm_9774060960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H7mm_9774070960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H8mm_9774080960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-3.3mm_H9mm_9774090960 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-4.5mm_H10mm_9774100982 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-4.5mm_H1mm_9774010982 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-4.5mm_H2mm_9774020982 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-4.5mm_H3mm_9774030982 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-4.5mm_H4mm_9774040982 +Mounting_Wuerth:Mounting_Wuerth_WA-SMST-4.5mm_H5mm_9774050982 +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 +NetTie:NetTie-2_THT_Pad1.0mm +NetTie:NetTie-3_SMD_Pad0.5mm +NetTie:NetTie-3_SMD_Pad2.0mm +NetTie:NetTie-3_THT_Pad0.3mm +NetTie:NetTie-3_THT_Pad1.0mm +NetTie:NetTie-4_SMD_Pad0.5mm +NetTie:NetTie-4_SMD_Pad2.0mm +NetTie:NetTie-4_THT_Pad0.3mm +NetTie:NetTie-4_THT_Pad1.0mm +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 +OptoDevice:Everlight_ITR8307_Reverse +OptoDevice:Everlight_ITR9608-F +OptoDevice:Finder_34.81 +OptoDevice:Hamamatsu_C12880 +OptoDevice:Hamamatsu_S13360-30CS +OptoDevice:Kingbright_KPS-3227 +OptoDevice:Kingbright_KPS-5130 +OptoDevice:Kingbright_KRC011_Horizontal +OptoDevice:Kingbright_KRC011_Vertical +OptoDevice:Kodenshi_LG206D +OptoDevice:Kodenshi_LG206L +OptoDevice:Kodenshi_SG105 +OptoDevice:Kodenshi_SG105F +OptoDevice:Kodenshi_SG105_Reverse +OptoDevice:LaserDiode_TO18-D5.6-3 +OptoDevice:LaserDiode_TO3.3-D3.3-3 +OptoDevice:LaserDiode_TO38ICut-3 +OptoDevice:LaserDiode_TO5-D9-3 +OptoDevice:LaserDiode_TO56-3 +OptoDevice:Lightpipe_Bivar_RLP1-400-650 +OptoDevice:Lightpipe_Bivar_SLP3-150-100-F +OptoDevice:Lightpipe_Bivar_SLP3-150-100-R +OptoDevice:Lightpipe_Bivar_SLP3-150-150-F +OptoDevice:Lightpipe_Bivar_SLP3-150-150-R +OptoDevice:Lightpipe_Bivar_SLP3-150-200-R +OptoDevice:Lightpipe_Bivar_SLP3-150-250-F +OptoDevice:Lightpipe_Bivar_SLP3-150-250-R +OptoDevice:Lightpipe_Bivar_SLP3-150-300-F +OptoDevice:Lightpipe_Bivar_SLP3-150-300-R +OptoDevice:Lightpipe_Bivar_SLP3-150-450-R +OptoDevice:Lightpipe_Dialight_515-1064F +OptoDevice:Lightpipe_LPF-C012303S +OptoDevice:Lightpipe_LPF-C013301S +OptoDevice:Lightpipe_Mentor_1275.x00x +OptoDevice:Lightpipe_Mentor_1276.1004 +OptoDevice:Lightpipe_Mentor_1276.2004 +OptoDevice:Lite-On_LTR-303ALS-01 +OptoDevice:Luna_NSL-32 +OptoDevice:Maxim_OLGA-14_3.3x5.6mm_P0.8mm +OptoDevice:OnSemi_CASE100AQ +OptoDevice:OnSemi_CASE100CY +OptoDevice:ONSemi_QSE15x +OptoDevice:Osram_BP104-SMD +OptoDevice:Osram_BPW34S-SMD +OptoDevice:Osram_BPW82 +OptoDevice:Osram_DIL2_4.3x4.65mm_P5.08mm +OptoDevice:Osram_LPT80A +OptoDevice:Osram_SFH205 +OptoDevice:Osram_SFH2201 +OptoDevice:Osram_SFH225 +OptoDevice:Osram_SFH2430 +OptoDevice:Osram_SFH2440 +OptoDevice:Osram_SFH3710 +OptoDevice:Osram_SFH9x0x +OptoDevice:Osram_SMD-SmartDIL +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 +OptoDevice:R_LDR_4.9x4.2mm_P2.54mm_Vertical +OptoDevice:R_LDR_5.0x4.1mm_P3mm_Vertical +OptoDevice:R_LDR_5.1x4.3mm_P3.4mm_Vertical +OptoDevice:R_LDR_5.2x5.2mm_P3.5mm_Horizontal +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:Toshiba_TORX170_TORX173_TORX193_TORX194 +OptoDevice:Toshiba_TOTX170_TOTX173_TOTX193_TOTX194 +OptoDevice:Vishay_CAST-3Pin +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 +Oscillator:Oscillator_DIP-8_LargePads +Oscillator:Oscillator_OCXO_Morion_MV267 +Oscillator:Oscillator_OCXO_Morion_MV317 +Oscillator:Oscillator_SeikoEpson_SG-8002DB +Oscillator:Oscillator_SeikoEpson_SG-8002DC +Oscillator:Oscillator_SMD_Abracon_ABLNO +Oscillator:Oscillator_SMD_Abracon_ASCO-4Pin_1.6x1.2mm +Oscillator:Oscillator_SMD_Abracon_ASDMB-4Pin_2.5x2.0mm +Oscillator:Oscillator_SMD_Abracon_ASE-4Pin_3.2x2.5mm +Oscillator:Oscillator_SMD_Abracon_ASE-4Pin_3.2x2.5mm_HandSoldering +Oscillator:Oscillator_SMD_Abracon_ASV-4Pin_7.0x5.1mm +Oscillator:Oscillator_SMD_Abracon_ASV-4Pin_7.0x5.1mm_HandSoldering +Oscillator:Oscillator_SMD_Diodes_FN-4Pin_7.0x5.0mm +Oscillator:Oscillator_SMD_ECS_2520MV-xxx-xx-4Pin_2.5x2.0mm +Oscillator:Oscillator_SMD_EuroQuartz_XO32-4Pin_3.2x2.5mm +Oscillator:Oscillator_SMD_EuroQuartz_XO32-4Pin_3.2x2.5mm_HandSoldering +Oscillator:Oscillator_SMD_EuroQuartz_XO53-4Pin_5.0x3.2mm +Oscillator:Oscillator_SMD_EuroQuartz_XO53-4Pin_5.0x3.2mm_HandSoldering +Oscillator:Oscillator_SMD_EuroQuartz_XO91-4Pin_7.0x5.0mm +Oscillator:Oscillator_SMD_EuroQuartz_XO91-4Pin_7.0x5.0mm_HandSoldering +Oscillator:Oscillator_SMD_Fordahl_DFAS1-6Pin_14.8x9.1mm +Oscillator:Oscillator_SMD_Fordahl_DFAS11-4Pin_7.0x5.0mm +Oscillator:Oscillator_SMD_Fordahl_DFAS11-4Pin_7.0x5.0mm_HandSoldering +Oscillator:Oscillator_SMD_Fordahl_DFAS15-4Pin_5.0x3.2mm +Oscillator:Oscillator_SMD_Fordahl_DFAS15-4Pin_5.0x3.2mm_HandSoldering +Oscillator:Oscillator_SMD_Fordahl_DFAS2-4Pin_7.3x5.1mm +Oscillator:Oscillator_SMD_Fordahl_DFAS2-4Pin_7.3x5.1mm_HandSoldering +Oscillator:Oscillator_SMD_Fordahl_DFAS3-4Pin_9.1x7.2mm +Oscillator:Oscillator_SMD_Fordahl_DFAS3-4Pin_9.1x7.2mm_HandSoldering +Oscillator:Oscillator_SMD_Fordahl_DFAS7-4Pin_19.9x12.9mm +Oscillator:Oscillator_SMD_Fordahl_DFAS7-4Pin_19.9x12.9mm_HandSoldering +Oscillator:Oscillator_SMD_Fox_FT5H_5.0x3.2mm +Oscillator:Oscillator_SMD_IDT_JS6-6_5.0x3.2mm_P1.27mm +Oscillator:Oscillator_SMD_IDT_JU6-6_7.0x5.0mm_P2.54mm +Oscillator:Oscillator_SMD_IQD_IQXO70-4Pin_7.5x5.0mm +Oscillator:Oscillator_SMD_IQD_IQXO70-4Pin_7.5x5.0mm_HandSoldering +Oscillator:Oscillator_SMD_Kyocera_2520-6Pin_2.5x2.0mm +Oscillator:Oscillator_SMD_Kyocera_KC2520Z-4Pin_2.5x2.0mm +Oscillator:Oscillator_SMD_OCXO_ConnorWinfield_OH300 +Oscillator:Oscillator_SMD_SeikoEpson_SG210-4Pin_2.5x2.0mm +Oscillator:Oscillator_SMD_SeikoEpson_SG210-4Pin_2.5x2.0mm_HandSoldering +Oscillator:Oscillator_SMD_SeikoEpson_SG3030CM +Oscillator:Oscillator_SMD_SeikoEpson_SG8002CA-4Pin_7.0x5.0mm +Oscillator:Oscillator_SMD_SeikoEpson_SG8002CA-4Pin_7.0x5.0mm_HandSoldering +Oscillator:Oscillator_SMD_SeikoEpson_SG8002CE-4Pin_3.2x2.5mm +Oscillator:Oscillator_SMD_SeikoEpson_SG8002CE-4Pin_3.2x2.5mm_HandSoldering +Oscillator:Oscillator_SMD_SeikoEpson_SG8002JA-4Pin_14.0x8.7mm +Oscillator:Oscillator_SMD_SeikoEpson_SG8002JA-4Pin_14.0x8.7mm_HandSoldering +Oscillator:Oscillator_SMD_SeikoEpson_SG8002JC-4Pin_10.5x5.0mm +Oscillator:Oscillator_SMD_SeikoEpson_SG8002JC-4Pin_10.5x5.0mm_HandSoldering +Oscillator:Oscillator_SMD_SeikoEpson_SG8002LB-4Pin_5.0x3.2mm +Oscillator:Oscillator_SMD_SeikoEpson_SG8002LB-4Pin_5.0x3.2mm_HandSoldering +Oscillator:Oscillator_SMD_SeikoEpson_TG2520SMN-xxx-xxxxxx-4Pin_2.5x2.0mm +Oscillator:Oscillator_SMD_SI570_SI571_HandSoldering +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-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_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 +Package_BGA:BGA-144_13.0x13.0mm_Layout12x12_P1.0mm +Package_BGA:BGA-144_7.0x7.0mm_Layout13x13_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD +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-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.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 +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_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 +Package_BGA:BGA-676_27.0x27.0mm_Layout26x26_P1.0mm_Ball0.6mm_Pad0.5mm_NSMD +Package_BGA:BGA-68_5.0x5.0mm_Layout9x9_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD +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 +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-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-272_9x9mm_Layout17x17_P0.5mm +Package_BGA:MAPBGA-289_14x14mm_Layout17x17_P0.8mm +Package_BGA:Maxim_WLP-12 +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 +Package_BGA:Micron_FBGA-78_8x10.5mm_Layout9x13_P0.8mm +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 +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_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.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 +Package_BGA:Texas_DSBGA-6_0.9x1.4mm_Layout2x3_P0.5mm +Package_BGA:Texas_DSBGA-8_0.705x1.468mm_Layout2x4_P0.4mm +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_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 +Package_BGA:UFBGA-100_7x7mm_Layout12x12_P0.5mm +Package_BGA:UFBGA-132_7x7mm_Layout12x12_P0.5mm +Package_BGA:UFBGA-132_7x7mm_P0.5mm +Package_BGA:UFBGA-144_10x10mm_Layout12x12_P0.8mm +Package_BGA:UFBGA-144_7x7mm_Layout12x12_P0.5mm +Package_BGA:UFBGA-15_3.0x3.0mm_Layout4x4_P0.65mm +Package_BGA:UFBGA-169_7x7mm_Layout13x13_P0.5mm +Package_BGA:UFBGA-201_10x10mm_Layout15x15_P0.65mm +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 +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.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 +Package_BGA:XFBGA-64_5.0x5.0mm_Layout8x8_P0.5mm +Package_BGA:Xilinx_CLG225 +Package_BGA:Xilinx_CLG400 +Package_BGA:Xilinx_CLG484_CLG485 +Package_BGA:Xilinx_CPG236 +Package_BGA:Xilinx_CPG238 +Package_BGA:Xilinx_CPGA196 +Package_BGA:Xilinx_CSG324 +Package_BGA:Xilinx_CSG325 +Package_BGA:Xilinx_CSGA225 +Package_BGA:Xilinx_CSGA324 +Package_BGA:Xilinx_FBG484 +Package_BGA:Xilinx_FBG676 +Package_BGA:Xilinx_FBG900 +Package_BGA:Xilinx_FFG1156 +Package_BGA:Xilinx_FFG1157_FFG1158 +Package_BGA:Xilinx_FFG1761 +Package_BGA:Xilinx_FFG1926_FFG1927_FFG1928_FFG1930 +Package_BGA:Xilinx_FFG676 +Package_BGA:Xilinx_FFG900_FFG901 +Package_BGA:Xilinx_FFV1761 +Package_BGA:Xilinx_FGG484 +Package_BGA:Xilinx_FGG676 +Package_BGA:Xilinx_FGGA484 +Package_BGA:Xilinx_FGGA676 +Package_BGA:Xilinx_FHG1761 +Package_BGA:Xilinx_FLG1925_FLG1926_FLG1928_FLG1930 +Package_BGA:Xilinx_FTG256 +Package_BGA:Xilinx_FTGB196 +Package_BGA:Xilinx_RB484 +Package_BGA:Xilinx_RB676 +Package_BGA:Xilinx_RF1156 +Package_BGA:Xilinx_RF1157_RF1158 +Package_BGA:Xilinx_RF1761 +Package_BGA:Xilinx_RF1930 +Package_BGA:Xilinx_RF676 +Package_BGA:Xilinx_RF900 +Package_BGA:Xilinx_RFG676 +Package_BGA:Xilinx_RS484 +Package_BGA:Xilinx_SBG484 +Package_BGA:Xilinx_SBG485 +Package_CSP:Analog_LFCSP-16-1EP_4x4mm_P0.65mm_EP2.1x2.1mm +Package_CSP:Analog_LFCSP-16-1EP_4x4mm_P0.65mm_EP2.1x2.1mm_ThermalVias +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 +Package_CSP:LFCSP-16-1EP_3x3mm_P0.5mm_EP1.7x1.7mm_ThermalVias +Package_CSP:LFCSP-16-1EP_3x3mm_P0.5mm_EP1.854x1.854mm +Package_CSP:LFCSP-16-1EP_4x4mm_P0.65mm_EP2.1x2.1mm +Package_CSP:LFCSP-16-1EP_4x4mm_P0.65mm_EP2.4x2.4mm +Package_CSP:LFCSP-16-1EP_4x4mm_P0.65mm_EP2.4x2.4mm_ThermalVias +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 +Package_CSP:LFCSP-24-1EP_4x4mm_P0.5mm_EP2.5x2.5mm_ThermalVias +Package_CSP:LFCSP-28-1EP_5x5mm_P0.5mm_EP3.14x3.14mm +Package_CSP:LFCSP-28-1EP_5x5mm_P0.5mm_EP3.14x3.14mm_ThermalVias +Package_CSP:LFCSP-32-1EP_5x5mm_P0.5mm_EP3.1x3.1mm +Package_CSP:LFCSP-32-1EP_5x5mm_P0.5mm_EP3.1x3.1mm_ThermalVias +Package_CSP:LFCSP-32-1EP_5x5mm_P0.5mm_EP3.25x3.25mm +Package_CSP:LFCSP-32-1EP_5x5mm_P0.5mm_EP3.5x3.5mm +Package_CSP:LFCSP-32-1EP_5x5mm_P0.5mm_EP3.5x3.5mm_ThermalVias +Package_CSP:LFCSP-32-1EP_5x5mm_P0.5mm_EP3.6x3.6mm +Package_CSP:LFCSP-32-1EP_5x5mm_P0.5mm_EP3.6x3.6mm_ThermalVias +Package_CSP:LFCSP-40-1EP_6x6mm_P0.5mm_EP3.9x3.9mm +Package_CSP:LFCSP-40-1EP_6x6mm_P0.5mm_EP3.9x3.9mm_ThermalVias +Package_CSP:LFCSP-40-1EP_6x6mm_P0.5mm_EP4.65x4.65mm +Package_CSP:LFCSP-40-1EP_6x6mm_P0.5mm_EP4.65x4.65mm_ThermalVias +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 +Package_CSP:LFCSP-72-1EP_10x10mm_P0.5mm_EP5.3x5.3mm +Package_CSP:LFCSP-72-1EP_10x10mm_P0.5mm_EP5.3x5.3mm_ThermalVias +Package_CSP:LFCSP-72-1EP_10x10mm_P0.5mm_EP6.15x6.15mm +Package_CSP:LFCSP-8-1EP_3x2mm_P0.5mm_EP1.6x1.65mm +Package_CSP:LFCSP-8-1EP_3x3mm_P0.5mm_EP1.45x1.74mm +Package_CSP:LFCSP-8-1EP_3x3mm_P0.5mm_EP1.6x2.34mm +Package_CSP:LFCSP-8-1EP_3x3mm_P0.5mm_EP1.6x2.34mm_ThermalVias +Package_CSP:LFCSP-8_2x2mm_P0.5mm +Package_CSP:LFCSP-VQ-24-1EP_4x4mm_P0.5mm_EP2.642x2.642mm +Package_CSP:LFCSP-VQ-48-1EP_7x7mm_P0.5mm +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: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.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_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_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.3x3.38mm_Layout7x7_P0.4mm_Offcenter +Package_CSP:ST_WLCSP-49_Die423 +Package_CSP:ST_WLCSP-49_Die431 +Package_CSP:ST_WLCSP-49_Die433 +Package_CSP:ST_WLCSP-49_Die435 +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_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 +Package_CSP:ST_WLCSP-64_Die427 +Package_CSP:ST_WLCSP-64_Die435 +Package_CSP:ST_WLCSP-64_Die436 +Package_CSP:ST_WLCSP-64_Die441 +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.2x3.95mm_Layout18x10_P0.4mm_Stagger +Package_CSP:ST_WLCSP-90_Die413 +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_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_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_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 +Package_DFN_QFN:DFN-10-1EP_2x3mm_P0.5mm_EP0.64x2.4mm +Package_DFN_QFN:DFN-10-1EP_3x3mm_P0.5mm_EP1.55x2.48mm +Package_DFN_QFN:DFN-10-1EP_3x3mm_P0.5mm_EP1.58x2.35mm +Package_DFN_QFN:DFN-10-1EP_3x3mm_P0.5mm_EP1.58x2.35mm_ThermalVias +Package_DFN_QFN:DFN-10-1EP_3x3mm_P0.5mm_EP1.65x2.38mm +Package_DFN_QFN:DFN-10-1EP_3x3mm_P0.5mm_EP1.65x2.38mm_ThermalVias +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.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 +Package_DFN_QFN:DFN-12-1EP_3x4mm_P0.5mm_EP1.7x3.3mm +Package_DFN_QFN:DFN-12-1EP_4x4mm_P0.5mm_EP2.66x3.38mm +Package_DFN_QFN:DFN-12-1EP_4x4mm_P0.65mm_EP2.64x3.54mm +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_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 +Package_DFN_QFN:DFN-16-1EP_4x5mm_P0.5mm_EP2.44x4.34mm +Package_DFN_QFN:DFN-16-1EP_5x5mm_P0.5mm_EP3.46x4mm +Package_DFN_QFN:DFN-18-1EP_3x5mm_P0.5mm_EP1.66x4.4mm +Package_DFN_QFN:DFN-18-1EP_4x5mm_P0.5mm_EP2.44x4.34mm +Package_DFN_QFN:DFN-20-1EP_5x6mm_P0.5mm_EP3.24x4.24mm +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-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.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.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 +Package_DFN_QFN:DFN-8-1EP_4x4mm_P0.8mm_EP2.5x3.6mm +Package_DFN_QFN:DFN-8-1EP_6x5mm_P1.27mm_EP2x2mm +Package_DFN_QFN:DFN-8-1EP_6x5mm_P1.27mm_EP4x4mm +Package_DFN_QFN:DFN-8_2x2mm_P0.5mm +Package_DFN_QFN:DFN-S-8-1EP_6x5mm_P1.27mm +Package_DFN_QFN:DHVQFN-14-1EP_2.5x3mm_P0.5mm_EP1x1.5mm +Package_DFN_QFN:DHVQFN-16-1EP_2.5x3.5mm_P0.5mm_EP1x2mm +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 +Package_DFN_QFN:HVQFN-24-1EP_4x4mm_P0.5mm_EP2.5x2.5mm_ThermalVias +Package_DFN_QFN:HVQFN-24-1EP_4x4mm_P0.5mm_EP2.6x2.6mm +Package_DFN_QFN:HVQFN-24-1EP_4x4mm_P0.5mm_EP2.6x2.6mm_ThermalVias +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 +Package_DFN_QFN:Infineon_MLPQ-48-1EP_7x7mm_P0.5mm_EP5.55x5.55mm +Package_DFN_QFN:Infineon_PQFN-22-15-4EP_6x5mm_P0.65mm +Package_DFN_QFN:Infineon_PQFN-44-31-5EP_7x7mm_P0.5mm +Package_DFN_QFN:Linear_DE14MA +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 +Package_DFN_QFN:Micrel_MLF-8-1EP_2x2mm_P0.5mm_EP0.8x1.3mm_ThermalVias +Package_DFN_QFN:Microchip_8E-16 +Package_DFN_QFN:Microchip_DRQFN-44-1EP_5x5mm_P0.7mm_EP2.65x2.65mm +Package_DFN_QFN:Microchip_DRQFN-44-1EP_5x5mm_P0.7mm_EP2.65x2.65mm_ThermalVias +Package_DFN_QFN:Microchip_DRQFN-64-1EP_7x7mm_P0.65mm_EP4.1x4.1mm +Package_DFN_QFN:Microchip_DRQFN-64-1EP_7x7mm_P0.65mm_EP4.1x4.1mm_ThermalVias +Package_DFN_QFN:Microsemi_QFN-40-32-2EP_6x8mm_P0.5mm +Package_DFN_QFN:Mini-Circuits_DL805 +Package_DFN_QFN:Mini-Circuits_FG873-4_3x3mm +Package_DFN_QFN:MLF-20-1EP_4x4mm_P0.5mm_EP2.6x2.6mm +Package_DFN_QFN:MLF-20-1EP_4x4mm_P0.5mm_EP2.6x2.6mm_ThermalVias +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 +Package_DFN_QFN:OnSemi_XDFN4-1EP_1.0x1.0mm_EP0.52x0.52mm +Package_DFN_QFN:Panasonic_HQFN-16-1EP_4x4mm_P0.65mm_EP2.9x2.9mm +Package_DFN_QFN:Panasonic_HSON-8_8x8mm_P2.00mm +Package_DFN_QFN:PQFN-8-EP_6x5mm_P1.27mm_Generic +Package_DFN_QFN:QFN-12-1EP_3x3mm_P0.51mm_EP1.45x1.45mm +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 +Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.7x1.7mm_ThermalVias +Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.9x1.9mm +Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.9x1.9mm_ThermalVias +Package_DFN_QFN:QFN-16-1EP_4x4mm_P0.5mm_EP2.45x2.45mm +Package_DFN_QFN:QFN-16-1EP_4x4mm_P0.5mm_EP2.45x2.45mm_ThermalVias +Package_DFN_QFN:QFN-16-1EP_4x4mm_P0.65mm_EP2.15x2.15mm +Package_DFN_QFN:QFN-16-1EP_4x4mm_P0.65mm_EP2.15x2.15mm_ThermalVias +Package_DFN_QFN:QFN-16-1EP_4x4mm_P0.65mm_EP2.1x2.1mm +Package_DFN_QFN:QFN-16-1EP_4x4mm_P0.65mm_EP2.1x2.1mm_ThermalVias +Package_DFN_QFN:QFN-16-1EP_4x4mm_P0.65mm_EP2.5x2.5mm +Package_DFN_QFN:QFN-16-1EP_4x4mm_P0.65mm_EP2.5x2.5mm_ThermalVias +Package_DFN_QFN:QFN-16-1EP_4x4mm_P0.65mm_EP2.7x2.7mm +Package_DFN_QFN:QFN-16-1EP_4x4mm_P0.65mm_EP2.7x2.7mm_PullBack +Package_DFN_QFN:QFN-16-1EP_4x4mm_P0.65mm_EP2.7x2.7mm_PullBack_ThermalVias +Package_DFN_QFN:QFN-16-1EP_4x4mm_P0.65mm_EP2.7x2.7mm_ThermalVias +Package_DFN_QFN:QFN-16-1EP_5x5mm_P0.8mm_EP2.7x2.7mm +Package_DFN_QFN:QFN-16-1EP_5x5mm_P0.8mm_EP2.7x2.7mm_ThermalVias +Package_DFN_QFN:QFN-20-1EP_3.5x3.5mm_P0.5mm_EP2x2mm +Package_DFN_QFN:QFN-20-1EP_3.5x3.5mm_P0.5mm_EP2x2mm_ThermalVias +Package_DFN_QFN:QFN-20-1EP_3x3mm_P0.45mm_EP1.6x1.6mm +Package_DFN_QFN:QFN-20-1EP_3x3mm_P0.45mm_EP1.6x1.6mm_ThermalVias +Package_DFN_QFN:QFN-20-1EP_3x3mm_P0.4mm_EP1.65x1.65mm +Package_DFN_QFN:QFN-20-1EP_3x3mm_P0.4mm_EP1.65x1.65mm_ThermalVias +Package_DFN_QFN:QFN-20-1EP_3x4mm_P0.5mm_EP1.65x2.65mm +Package_DFN_QFN:QFN-20-1EP_3x4mm_P0.5mm_EP1.65x2.65mm_ThermalVias +Package_DFN_QFN:QFN-20-1EP_4x4mm_P0.5mm_EP2.5x2.5mm +Package_DFN_QFN:QFN-20-1EP_4x4mm_P0.5mm_EP2.5x2.5mm_ThermalVias +Package_DFN_QFN:QFN-20-1EP_4x4mm_P0.5mm_EP2.6x2.6mm +Package_DFN_QFN:QFN-20-1EP_4x4mm_P0.5mm_EP2.6x2.6mm_ThermalVias +Package_DFN_QFN:QFN-20-1EP_4x4mm_P0.5mm_EP2.7x2.7mm +Package_DFN_QFN:QFN-20-1EP_4x4mm_P0.5mm_EP2.7x2.7mm_ThermalVias +Package_DFN_QFN:QFN-20-1EP_4x5mm_P0.5mm_EP2.65x3.65mm +Package_DFN_QFN:QFN-20-1EP_4x5mm_P0.5mm_EP2.65x3.65mm_ThermalVias +Package_DFN_QFN:QFN-20-1EP_5x5mm_P0.65mm_EP3.35x3.35mm +Package_DFN_QFN:QFN-20-1EP_5x5mm_P0.65mm_EP3.35x3.35mm_ThermalVias +Package_DFN_QFN:QFN-24-1EP_3x3mm_P0.4mm_EP1.75x1.6mm +Package_DFN_QFN:QFN-24-1EP_3x3mm_P0.4mm_EP1.75x1.6mm_ThermalVias +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 +Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.6x2.6mm_ThermalVias +Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.75x2.75mm +Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.75x2.75mm_ThermalVias +Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.7x2.6mm +Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.7x2.6mm_ThermalVias +Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.7x2.7mm +Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.7x2.7mm_ThermalVias +Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.8x2.8mm +Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.8x2.8mm_ThermalVias +Package_DFN_QFN:QFN-24-1EP_4x5mm_P0.5mm_EP2.65x3.65mm +Package_DFN_QFN:QFN-24-1EP_4x5mm_P0.5mm_EP2.65x3.65mm_ThermalVias +Package_DFN_QFN:QFN-24-1EP_5x5mm_P0.65mm_EP3.25x3.25mm +Package_DFN_QFN:QFN-24-1EP_5x5mm_P0.65mm_EP3.25x3.25mm_ThermalVias +Package_DFN_QFN:QFN-24-1EP_5x5mm_P0.65mm_EP3.2x3.2mm +Package_DFN_QFN:QFN-24-1EP_5x5mm_P0.65mm_EP3.2x3.2mm_ThermalVias +Package_DFN_QFN:QFN-24-1EP_5x5mm_P0.65mm_EP3.4x3.4mm +Package_DFN_QFN:QFN-24-1EP_5x5mm_P0.65mm_EP3.4x3.4mm_ThermalVias +Package_DFN_QFN:QFN-24-1EP_5x5mm_P0.65mm_EP3.6x3.6mm +Package_DFN_QFN:QFN-24-1EP_5x5mm_P0.65mm_EP3.6x3.6mm_ThermalVias +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 +Package_DFN_QFN:QFN-28-1EP_5x5mm_P0.5mm_EP3.75x3.75mm_ThermalVias +Package_DFN_QFN:QFN-28-1EP_5x6mm_P0.5mm_EP3.65x4.65mm +Package_DFN_QFN:QFN-28-1EP_5x6mm_P0.5mm_EP3.65x4.65mm_ThermalVias +Package_DFN_QFN:QFN-28-1EP_6x6mm_P0.65mm_EP4.25x4.25mm +Package_DFN_QFN:QFN-28-1EP_6x6mm_P0.65mm_EP4.25x4.25mm_ThermalVias +Package_DFN_QFN:QFN-28-1EP_6x6mm_P0.65mm_EP4.8x4.8mm +Package_DFN_QFN:QFN-28-1EP_6x6mm_P0.65mm_EP4.8x4.8mm_ThermalVias +Package_DFN_QFN:QFN-28_4x4mm_P0.5mm +Package_DFN_QFN:QFN-32-1EP_4x4mm_P0.4mm_EP2.65x2.65mm +Package_DFN_QFN:QFN-32-1EP_4x4mm_P0.4mm_EP2.65x2.65mm_ThermalVias +Package_DFN_QFN:QFN-32-1EP_4x4mm_P0.4mm_EP2.9x2.9mm +Package_DFN_QFN:QFN-32-1EP_4x4mm_P0.4mm_EP2.9x2.9mm_ThermalVias +Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.1x3.1mm +Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.1x3.1mm_ThermalVias +Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.3x3.3mm +Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.3x3.3mm_ThermalVias +Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.45x3.45mm +Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.45x3.45mm_ThermalVias +Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.65x3.65mm +Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.65x3.65mm_ThermalVias +Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.6x3.6mm +Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.6x3.6mm_ThermalVias +Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.7x3.7mm +Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.7x3.7mm_ThermalVias +Package_DFN_QFN:QFN-32-1EP_7x7mm_P0.65mm_EP4.65x4.65mm +Package_DFN_QFN:QFN-32-1EP_7x7mm_P0.65mm_EP4.65x4.65mm_ThermalVias +Package_DFN_QFN:QFN-32-1EP_7x7mm_P0.65mm_EP4.7x4.7mm +Package_DFN_QFN:QFN-32-1EP_7x7mm_P0.65mm_EP4.7x4.7mm_ThermalVias +Package_DFN_QFN:QFN-32-1EP_7x7mm_P0.65mm_EP5.4x5.4mm +Package_DFN_QFN:QFN-32-1EP_7x7mm_P0.65mm_EP5.4x5.4mm_ThermalVias +Package_DFN_QFN:QFN-36-1EP_5x6mm_P0.5mm_EP3.6x4.1mm +Package_DFN_QFN:QFN-36-1EP_5x6mm_P0.5mm_EP3.6x4.1mm_ThermalVias +Package_DFN_QFN:QFN-36-1EP_5x6mm_P0.5mm_EP3.6x4.6mm +Package_DFN_QFN:QFN-36-1EP_5x6mm_P0.5mm_EP3.6x4.6mm_ThermalVias +Package_DFN_QFN:QFN-36-1EP_6x6mm_P0.5mm_EP3.7x3.7mm +Package_DFN_QFN:QFN-36-1EP_6x6mm_P0.5mm_EP3.7x3.7mm_ThermalVias +Package_DFN_QFN:QFN-36-1EP_6x6mm_P0.5mm_EP4.1x4.1mm +Package_DFN_QFN:QFN-36-1EP_6x6mm_P0.5mm_EP4.1x4.1mm_ThermalVias +Package_DFN_QFN:QFN-38-1EP_4x6mm_P0.4mm_EP2.65x4.65mm +Package_DFN_QFN:QFN-38-1EP_4x6mm_P0.4mm_EP2.65x4.65mm_ThermalVias +Package_DFN_QFN:QFN-38-1EP_5x7mm_P0.5mm_EP3.15x5.15mm +Package_DFN_QFN:QFN-38-1EP_5x7mm_P0.5mm_EP3.15x5.15mm_ThermalVias +Package_DFN_QFN:QFN-40-1EP_5x5mm_P0.4mm_EP3.6x3.6mm +Package_DFN_QFN:QFN-40-1EP_5x5mm_P0.4mm_EP3.6x3.6mm_ThermalVias +Package_DFN_QFN:QFN-40-1EP_5x5mm_P0.4mm_EP3.8x3.8mm +Package_DFN_QFN:QFN-40-1EP_5x5mm_P0.4mm_EP3.8x3.8mm_ThermalVias +Package_DFN_QFN:QFN-40-1EP_6x6mm_P0.5mm_EP4.6x4.6mm +Package_DFN_QFN:QFN-40-1EP_6x6mm_P0.5mm_EP4.6x4.6mm_ThermalVias +Package_DFN_QFN:QFN-42-1EP_5x6mm_P0.4mm_EP3.7x4.7mm +Package_DFN_QFN:QFN-42-1EP_5x6mm_P0.4mm_EP3.7x4.7mm_ThermalVias +Package_DFN_QFN:QFN-44-1EP_7x7mm_P0.5mm_EP5.15x5.15mm +Package_DFN_QFN:QFN-44-1EP_7x7mm_P0.5mm_EP5.15x5.15mm_ThermalVias +Package_DFN_QFN:QFN-44-1EP_7x7mm_P0.5mm_EP5.2x5.2mm +Package_DFN_QFN:QFN-44-1EP_7x7mm_P0.5mm_EP5.2x5.2mm_ThermalVias +Package_DFN_QFN:QFN-44-1EP_8x8mm_P0.65mm_EP6.45x6.45mm +Package_DFN_QFN:QFN-44-1EP_8x8mm_P0.65mm_EP6.45x6.45mm_ThermalVias +Package_DFN_QFN:QFN-44-1EP_9x9mm_P0.65mm_EP7.5x7.5mm +Package_DFN_QFN:QFN-44-1EP_9x9mm_P0.65mm_EP7.5x7.5mm_ThermalVias +Package_DFN_QFN:QFN-48-1EP_5x5mm_P0.35mm_EP3.7x3.7mm +Package_DFN_QFN:QFN-48-1EP_5x5mm_P0.35mm_EP3.7x3.7mm_ThermalVias +Package_DFN_QFN:QFN-48-1EP_6x6mm_P0.4mm_EP4.2x4.2mm +Package_DFN_QFN:QFN-48-1EP_6x6mm_P0.4mm_EP4.2x4.2mm_ThermalVias +Package_DFN_QFN:QFN-48-1EP_6x6mm_P0.4mm_EP4.3x4.3mm +Package_DFN_QFN:QFN-48-1EP_6x6mm_P0.4mm_EP4.3x4.3mm_ThermalVias +Package_DFN_QFN:QFN-48-1EP_6x6mm_P0.4mm_EP4.66x4.66mm +Package_DFN_QFN:QFN-48-1EP_6x6mm_P0.4mm_EP4.66x4.66mm_ThermalVias +Package_DFN_QFN:QFN-48-1EP_6x6mm_P0.4mm_EP4.6x4.6mm +Package_DFN_QFN:QFN-48-1EP_6x6mm_P0.4mm_EP4.6x4.6mm_ThermalVias +Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP3.5x3.5mm +Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP3.5x3.5mm_ThermalVias +Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.15x5.15mm +Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.15x5.15mm_ThermalVias +Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.1x5.1mm +Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.1x5.1mm_ThermalVias +Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.3x5.3mm +Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.3x5.3mm_ThermalVias +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 +Package_DFN_QFN:QFN-52-1EP_7x8mm_P0.5mm_EP5.41x6.45mm_ThermalVias +Package_DFN_QFN:QFN-56-1EP_7x7mm_P0.4mm_EP3.2x3.2mm +Package_DFN_QFN:QFN-56-1EP_7x7mm_P0.4mm_EP3.2x3.2mm_ThermalVias +Package_DFN_QFN:QFN-56-1EP_7x7mm_P0.4mm_EP4x4mm +Package_DFN_QFN:QFN-56-1EP_7x7mm_P0.4mm_EP4x4mm_ThermalVias +Package_DFN_QFN:QFN-56-1EP_7x7mm_P0.4mm_EP5.6x5.6mm +Package_DFN_QFN:QFN-56-1EP_7x7mm_P0.4mm_EP5.6x5.6mm_ThermalVias +Package_DFN_QFN:QFN-56-1EP_8x8mm_P0.5mm_EP4.3x4.3mm +Package_DFN_QFN:QFN-56-1EP_8x8mm_P0.5mm_EP4.3x4.3mm_ThermalVias +Package_DFN_QFN:QFN-56-1EP_8x8mm_P0.5mm_EP4.5x5.2mm +Package_DFN_QFN:QFN-56-1EP_8x8mm_P0.5mm_EP4.5x5.2mm_ThermalVias +Package_DFN_QFN:QFN-56-1EP_8x8mm_P0.5mm_EP4.5x5.2mm_ThermalVias_TopTented +Package_DFN_QFN:QFN-56-1EP_8x8mm_P0.5mm_EP5.6x5.6mm +Package_DFN_QFN:QFN-56-1EP_8x8mm_P0.5mm_EP5.6x5.6mm_ThermalVias +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 +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP3.4x3.4mm_ThermalVias +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP3.8x3.8mm +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP3.8x3.8mm_ThermalVias +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP4.1x4.1mm +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP4.1x4.1mm_ThermalVias +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP4.35x4.35mm +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP4.35x4.35mm_ThermalVias +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP4.7x4.7mm +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP4.7x4.7mm_ThermalVias +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP5.2x5.2mm +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP5.2x5.2mm_ThermalVias +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 +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP7.15x7.15mm_ThermalVias +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP7.25x7.25mm +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP7.35x7.35mm +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP7.3x7.3mm +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP7.3x7.3mm_ThermalVias +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP7.5x7.5mm +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP7.5x7.5mm_ThermalVias +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP7.65x7.65mm +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP7.65x7.65mm_ThermalVias +Package_DFN_QFN:QFN-68-1EP_8x8mm_P0.4mm_EP5.2x5.2mm +Package_DFN_QFN:QFN-68-1EP_8x8mm_P0.4mm_EP5.2x5.2mm_ThermalVias +Package_DFN_QFN:QFN-68-1EP_8x8mm_P0.4mm_EP6.4x6.4mm +Package_DFN_QFN:QFN-68-1EP_8x8mm_P0.4mm_EP6.4x6.4mm_ThermalVias +Package_DFN_QFN:QFN-72-1EP_10x10mm_P0.5mm_EP6x6mm +Package_DFN_QFN:QFN-72-1EP_10x10mm_P0.5mm_EP6x6mm_ThermalVias +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 +Package_DFN_QFN:TDFN-10-1EP_2x3mm_P0.5mm_EP0.9x2mm_ThermalVias +Package_DFN_QFN:TDFN-12-1EP_3x3mm_P0.4mm_EP1.7x2.45mm +Package_DFN_QFN:TDFN-12-1EP_3x3mm_P0.4mm_EP1.7x2.45mm_ThermalVias +Package_DFN_QFN:TDFN-12_2x3mm_P0.5mm +Package_DFN_QFN:TDFN-14-1EP_3x3mm_P0.4mm_EP1.78x2.35mm +Package_DFN_QFN:TDFN-14-1EP_3x3mm_P0.4mm_EP1.78x2.35mm_ThermalVias +Package_DFN_QFN:TDFN-6-1EP_2.5x2.5mm_P0.65mm_EP1.3x2mm +Package_DFN_QFN:TDFN-6-1EP_2.5x2.5mm_P0.65mm_EP1.3x2mm_ThermalVias +Package_DFN_QFN:TDFN-8-1EP_2x2mm_P0.5mm_EP0.8x1.2mm +Package_DFN_QFN:TDFN-8-1EP_3x2mm_P0.5mm_EP1.3x1.4mm +Package_DFN_QFN:TDFN-8-1EP_3x2mm_P0.5mm_EP1.4x1.4mm +Package_DFN_QFN:TDFN-8-1EP_3x2mm_P0.5mm_EP1.80x1.65mm +Package_DFN_QFN:TDFN-8-1EP_3x2mm_P0.5mm_EP1.80x1.65mm_ThermalVias +Package_DFN_QFN:TDFN-8_1.4x1.6mm_P0.4mm +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_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_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-PWQFN-N100_EP5.5x5.5mm +Package_DFN_QFN:Texas_S-PWQFN-N100_EP5.5x5.5mm_ThermalVias +Package_DFN_QFN:Texas_S-PWQFN-N20 +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 +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_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 +Package_DFN_QFN:TQFN-16-1EP_5x5mm_P0.8mm_EP2.29x2.29mm +Package_DFN_QFN:TQFN-16-1EP_5x5mm_P0.8mm_EP2.29x2.29mm_ThermalVias +Package_DFN_QFN:TQFN-16-1EP_5x5mm_P0.8mm_EP3.1x3.1mm +Package_DFN_QFN:TQFN-16-1EP_5x5mm_P0.8mm_EP3.1x3.1mm_ThermalVias +Package_DFN_QFN:TQFN-20-1EP_4x4mm_P0.5mm_EP2.1x2.1mm +Package_DFN_QFN:TQFN-20-1EP_4x4mm_P0.5mm_EP2.1x2.1mm_ThermalVias +Package_DFN_QFN:TQFN-20-1EP_4x4mm_P0.5mm_EP2.7x2.7mm +Package_DFN_QFN:TQFN-20-1EP_4x4mm_P0.5mm_EP2.7x2.7mm_ThermalVias +Package_DFN_QFN:TQFN-20-1EP_4x4mm_P0.5mm_EP2.9x2.9mm +Package_DFN_QFN:TQFN-20-1EP_4x4mm_P0.5mm_EP2.9x2.9mm_ThermalVias +Package_DFN_QFN:TQFN-20-1EP_5x5mm_P0.65mm_EP3.1x3.1mm +Package_DFN_QFN:TQFN-20-1EP_5x5mm_P0.65mm_EP3.1x3.1mm_ThermalVias +Package_DFN_QFN:TQFN-20-1EP_5x5mm_P0.65mm_EP3.25x3.25mm +Package_DFN_QFN:TQFN-20-1EP_5x5mm_P0.65mm_EP3.25x3.25mm_ThermalVias +Package_DFN_QFN:TQFN-24-1EP_4x4mm_P0.5mm_EP2.1x2.1mm +Package_DFN_QFN:TQFN-24-1EP_4x4mm_P0.5mm_EP2.1x2.1mm_ThermalVias +Package_DFN_QFN:TQFN-24-1EP_4x4mm_P0.5mm_EP2.6x2.6mm +Package_DFN_QFN:TQFN-24-1EP_4x4mm_P0.5mm_EP2.6x2.6mm_ThermalVias +Package_DFN_QFN:TQFN-24-1EP_4x4mm_P0.5mm_EP2.8x2.8mm_PullBack +Package_DFN_QFN:TQFN-24-1EP_4x4mm_P0.5mm_EP2.8x2.8mm_PullBack_ThermalVias +Package_DFN_QFN:TQFN-28-1EP_5x5mm_P0.5mm_EP2.7x2.7mm +Package_DFN_QFN:TQFN-28-1EP_5x5mm_P0.5mm_EP2.7x2.7mm_ThermalVias +Package_DFN_QFN:TQFN-28-1EP_5x5mm_P0.5mm_EP3.25x3.25mm +Package_DFN_QFN:TQFN-28-1EP_5x5mm_P0.5mm_EP3.25x3.25mm_ThermalVias +Package_DFN_QFN:TQFN-32-1EP_5x5mm_P0.5mm_EP2.1x2.1mm +Package_DFN_QFN:TQFN-32-1EP_5x5mm_P0.5mm_EP2.1x2.1mm_ThermalVias +Package_DFN_QFN:TQFN-32-1EP_5x5mm_P0.5mm_EP3.1x3.1mm +Package_DFN_QFN:TQFN-32-1EP_5x5mm_P0.5mm_EP3.1x3.1mm_ThermalVias +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 +Package_DFN_QFN:UFQFPN-32-1EP_5x5mm_P0.5mm_EP3.5x3.5mm +Package_DFN_QFN:UFQFPN-32-1EP_5x5mm_P0.5mm_EP3.5x3.5mm_ThermalVias +Package_DFN_QFN:UQFN-10_1.3x1.8mm_P0.4mm +Package_DFN_QFN:UQFN-10_1.4x1.8mm_P0.4mm +Package_DFN_QFN:UQFN-10_1.6x2.1mm_P0.5mm +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 +Package_DFN_QFN:UQFN-20-1EP_4x4mm_P0.5mm_EP2.8x2.8mm_ThermalVias +Package_DFN_QFN:UQFN-20_3x3mm_P0.4mm +Package_DFN_QFN:UQFN-28-1EP_4x4mm_P0.4mm_EP2.35x2.35mm +Package_DFN_QFN:UQFN-28-1EP_4x4mm_P0.4mm_EP2.35x2.35mm_ThermalVias +Package_DFN_QFN:UQFN-40-1EP_5x5mm_P0.4mm_EP3.8x3.8mm +Package_DFN_QFN:UQFN-40-1EP_5x5mm_P0.4mm_EP3.8x3.8mm_ThermalVias +Package_DFN_QFN:UQFN-48-1EP_6x6mm_P0.4mm_EP4.45x4.45mm +Package_DFN_QFN:UQFN-48-1EP_6x6mm_P0.4mm_EP4.45x4.45mm_ThermalVias +Package_DFN_QFN:UQFN-48-1EP_6x6mm_P0.4mm_EP4.62x4.62mm +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 +Package_DFN_QFN:VQFN-16-1EP_3x3mm_P0.5mm_EP1.45x1.45mm_ThermalVias +Package_DFN_QFN:VQFN-16-1EP_3x3mm_P0.5mm_EP1.68x1.68mm +Package_DFN_QFN:VQFN-16-1EP_3x3mm_P0.5mm_EP1.68x1.68mm_ThermalVias +Package_DFN_QFN:VQFN-16-1EP_3x3mm_P0.5mm_EP1.6x1.6mm +Package_DFN_QFN:VQFN-16-1EP_3x3mm_P0.5mm_EP1.6x1.6mm_ThermalVias +Package_DFN_QFN:VQFN-16-1EP_3x3mm_P0.5mm_EP1.8x1.8mm +Package_DFN_QFN:VQFN-16-1EP_3x3mm_P0.5mm_EP1.8x1.8mm_ThermalVias +Package_DFN_QFN:VQFN-20-1EP_3x3mm_P0.45mm_EP1.55x1.55mm +Package_DFN_QFN:VQFN-20-1EP_3x3mm_P0.45mm_EP1.55x1.55mm_ThermalVias +Package_DFN_QFN:VQFN-20-1EP_3x3mm_P0.4mm_EP1.7x1.7mm +Package_DFN_QFN:VQFN-20-1EP_3x3mm_P0.4mm_EP1.7x1.7mm_ThermalVias +Package_DFN_QFN:VQFN-24-1EP_4x4mm_P0.5mm_EP2.45x2.45mm +Package_DFN_QFN:VQFN-24-1EP_4x4mm_P0.5mm_EP2.45x2.45mm_ThermalVias +Package_DFN_QFN:VQFN-24-1EP_4x4mm_P0.5mm_EP2.5x2.5mm +Package_DFN_QFN:VQFN-24-1EP_4x4mm_P0.5mm_EP2.5x2.5mm_ThermalVias +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 +Package_DFN_QFN:VQFN-48-1EP_6x6mm_P0.4mm_EP4.1x4.1mm_ThermalVias +Package_DFN_QFN:VQFN-48-1EP_7x7mm_P0.5mm_EP4.1x4.1mm +Package_DFN_QFN:VQFN-48-1EP_7x7mm_P0.5mm_EP4.1x4.1mm_ThermalVias +Package_DFN_QFN:VQFN-48-1EP_7x7mm_P0.5mm_EP5.15x5.15mm +Package_DFN_QFN:VQFN-48-1EP_7x7mm_P0.5mm_EP5.15x5.15mm_ThermalVias +Package_DFN_QFN:VQFN-64-1EP_9x9mm_P0.5mm_EP5.4x5.4mm +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 +Package_DFN_QFN:WDFN-6-2EP_4.0x2.6mm_P0.65mm +Package_DFN_QFN:WDFN-8-1EP_2x2.2mm_P0.5mm_EP0.80x0.54 +Package_DFN_QFN:WDFN-8-1EP_2x2mm_P0.5mm_EP0.8x1.2mm +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 +Package_DFN_QFN:WQFN-16-1EP_3x3mm_P0.5mm_EP1.68x1.68mm_ThermalVias +Package_DFN_QFN:WQFN-16-1EP_3x3mm_P0.5mm_EP1.6x1.6mm +Package_DFN_QFN:WQFN-16-1EP_3x3mm_P0.5mm_EP1.6x1.6mm_ThermalVias +Package_DFN_QFN:WQFN-16-1EP_3x3mm_P0.5mm_EP1.75x1.75mm +Package_DFN_QFN:WQFN-16-1EP_3x3mm_P0.5mm_EP1.75x1.75mm_ThermalVias +Package_DFN_QFN:WQFN-16-1EP_4x4mm_P0.5mm_EP2.6x2.6mm +Package_DFN_QFN:WQFN-16-1EP_4x4mm_P0.5mm_EP2.6x2.6mm_ThermalVias +Package_DFN_QFN:WQFN-20-1EP_2.5x4.5mm_P0.5mm_EP1x2.9mm +Package_DFN_QFN:WQFN-20-1EP_3x3mm_P0.4mm_EP1.7x1.7mm +Package_DFN_QFN:WQFN-20-1EP_3x3mm_P0.4mm_EP1.7x1.7mm_ThermalVias +Package_DFN_QFN:WQFN-24-1EP_4x4mm_P0.5mm_EP2.45x2.45mm +Package_DFN_QFN:WQFN-24-1EP_4x4mm_P0.5mm_EP2.45x2.45mm_ThermalVias +Package_DFN_QFN:WQFN-24-1EP_4x4mm_P0.5mm_EP2.6x2.6mm +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 +Package_DIP:DIP-10_W7.62mm_LongPads +Package_DIP:DIP-10_W7.62mm_SMDSocket_SmallPads +Package_DIP:DIP-10_W7.62mm_Socket +Package_DIP:DIP-10_W7.62mm_Socket_LongPads +Package_DIP:DIP-10_W8.89mm_SMDSocket_LongPads +Package_DIP:DIP-12_W10.16mm +Package_DIP:DIP-12_W10.16mm_LongPads +Package_DIP:DIP-12_W7.62mm +Package_DIP:DIP-12_W7.62mm_LongPads +Package_DIP:DIP-12_W7.62mm_SMDSocket_SmallPads +Package_DIP:DIP-12_W7.62mm_Socket +Package_DIP:DIP-12_W7.62mm_Socket_LongPads +Package_DIP:DIP-12_W8.89mm_SMDSocket_LongPads +Package_DIP:DIP-14_W10.16mm +Package_DIP:DIP-14_W10.16mm_LongPads +Package_DIP:DIP-14_W7.62mm +Package_DIP:DIP-14_W7.62mm_LongPads +Package_DIP:DIP-14_W7.62mm_SMDSocket_SmallPads +Package_DIP:DIP-14_W7.62mm_Socket +Package_DIP:DIP-14_W7.62mm_Socket_LongPads +Package_DIP:DIP-14_W8.89mm_SMDSocket_LongPads +Package_DIP:DIP-16_W10.16mm +Package_DIP:DIP-16_W10.16mm_LongPads +Package_DIP:DIP-16_W7.62mm +Package_DIP:DIP-16_W7.62mm_LongPads +Package_DIP:DIP-16_W7.62mm_SMDSocket_SmallPads +Package_DIP:DIP-16_W7.62mm_Socket +Package_DIP:DIP-16_W7.62mm_Socket_LongPads +Package_DIP:DIP-16_W8.89mm_SMDSocket_LongPads +Package_DIP:DIP-18_W7.62mm +Package_DIP:DIP-18_W7.62mm_LongPads +Package_DIP:DIP-18_W7.62mm_SMDSocket_SmallPads +Package_DIP:DIP-18_W7.62mm_Socket +Package_DIP:DIP-18_W7.62mm_Socket_LongPads +Package_DIP:DIP-18_W8.89mm_SMDSocket_LongPads +Package_DIP:DIP-20_W7.62mm +Package_DIP:DIP-20_W7.62mm_LongPads +Package_DIP:DIP-20_W7.62mm_SMDSocket_SmallPads +Package_DIP:DIP-20_W7.62mm_Socket +Package_DIP:DIP-20_W7.62mm_Socket_LongPads +Package_DIP:DIP-20_W8.89mm_SMDSocket_LongPads +Package_DIP:DIP-22_W10.16mm +Package_DIP:DIP-22_W10.16mm_LongPads +Package_DIP:DIP-22_W10.16mm_SMDSocket_SmallPads +Package_DIP:DIP-22_W10.16mm_Socket +Package_DIP:DIP-22_W10.16mm_Socket_LongPads +Package_DIP:DIP-22_W11.43mm_SMDSocket_LongPads +Package_DIP:DIP-22_W7.62mm +Package_DIP:DIP-22_W7.62mm_LongPads +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 +Package_DIP:DIP-24_W10.16mm_Socket +Package_DIP:DIP-24_W10.16mm_Socket_LongPads +Package_DIP:DIP-24_W11.43mm_SMDSocket_LongPads +Package_DIP:DIP-24_W15.24mm +Package_DIP:DIP-24_W15.24mm_LongPads +Package_DIP:DIP-24_W15.24mm_SMDSocket_SmallPads +Package_DIP:DIP-24_W15.24mm_Socket +Package_DIP:DIP-24_W15.24mm_Socket_LongPads +Package_DIP:DIP-24_W16.51mm_SMDSocket_LongPads +Package_DIP:DIP-24_W7.62mm +Package_DIP:DIP-24_W7.62mm_LongPads +Package_DIP:DIP-24_W7.62mm_SMDSocket_SmallPads +Package_DIP:DIP-24_W7.62mm_Socket +Package_DIP:DIP-24_W7.62mm_Socket_LongPads +Package_DIP:DIP-24_W8.89mm_SMDSocket_LongPads +Package_DIP:DIP-28_W15.24mm +Package_DIP:DIP-28_W15.24mm_LongPads +Package_DIP:DIP-28_W15.24mm_SMDSocket_SmallPads +Package_DIP:DIP-28_W15.24mm_Socket +Package_DIP:DIP-28_W15.24mm_Socket_LongPads +Package_DIP:DIP-28_W16.51mm_SMDSocket_LongPads +Package_DIP:DIP-28_W7.62mm +Package_DIP:DIP-28_W7.62mm_LongPads +Package_DIP:DIP-28_W7.62mm_SMDSocket_SmallPads +Package_DIP:DIP-28_W7.62mm_Socket +Package_DIP:DIP-28_W7.62mm_Socket_LongPads +Package_DIP:DIP-28_W8.89mm_SMDSocket_LongPads +Package_DIP:DIP-32_W15.24mm +Package_DIP:DIP-32_W15.24mm_LongPads +Package_DIP:DIP-32_W15.24mm_SMDSocket_SmallPads +Package_DIP:DIP-32_W15.24mm_Socket +Package_DIP:DIP-32_W15.24mm_Socket_LongPads +Package_DIP:DIP-32_W16.51mm_SMDSocket_LongPads +Package_DIP:DIP-32_W7.62mm +Package_DIP:DIP-40_W15.24mm +Package_DIP:DIP-40_W15.24mm_LongPads +Package_DIP:DIP-40_W15.24mm_SMDSocket_SmallPads +Package_DIP:DIP-40_W15.24mm_Socket +Package_DIP:DIP-40_W15.24mm_Socket_LongPads +Package_DIP:DIP-40_W16.51mm_SMDSocket_LongPads +Package_DIP:DIP-40_W25.4mm +Package_DIP:DIP-40_W25.4mm_LongPads +Package_DIP:DIP-40_W25.4mm_SMDSocket_SmallPads +Package_DIP:DIP-40_W25.4mm_Socket +Package_DIP:DIP-40_W25.4mm_Socket_LongPads +Package_DIP:DIP-40_W26.67mm_SMDSocket_LongPads +Package_DIP:DIP-42_W15.24mm +Package_DIP:DIP-42_W15.24mm_LongPads +Package_DIP:DIP-42_W15.24mm_SMDSocket_SmallPads +Package_DIP:DIP-42_W15.24mm_Socket +Package_DIP:DIP-42_W15.24mm_Socket_LongPads +Package_DIP:DIP-42_W16.51mm_SMDSocket_LongPads +Package_DIP:DIP-48_W15.24mm +Package_DIP:DIP-48_W15.24mm_LongPads +Package_DIP:DIP-48_W15.24mm_SMDSocket_SmallPads +Package_DIP:DIP-48_W15.24mm_Socket +Package_DIP:DIP-48_W15.24mm_Socket_LongPads +Package_DIP:DIP-48_W16.51mm_SMDSocket_LongPads +Package_DIP:DIP-4_W10.16mm +Package_DIP:DIP-4_W10.16mm_LongPads +Package_DIP:DIP-4_W7.62mm +Package_DIP:DIP-4_W7.62mm_LongPads +Package_DIP:DIP-4_W7.62mm_SMDSocket_SmallPads +Package_DIP:DIP-4_W7.62mm_Socket +Package_DIP:DIP-4_W7.62mm_Socket_LongPads +Package_DIP:DIP-4_W8.89mm_SMDSocket_LongPads +Package_DIP:DIP-5-6_W10.16mm +Package_DIP:DIP-5-6_W10.16mm_LongPads +Package_DIP:DIP-5-6_W7.62mm +Package_DIP:DIP-5-6_W7.62mm_LongPads +Package_DIP:DIP-5-6_W7.62mm_SMDSocket_SmallPads +Package_DIP:DIP-5-6_W7.62mm_Socket +Package_DIP:DIP-5-6_W7.62mm_Socket_LongPads +Package_DIP:DIP-5-6_W8.89mm_SMDSocket_LongPads +Package_DIP:DIP-64_W15.24mm +Package_DIP:DIP-64_W15.24mm_LongPads +Package_DIP:DIP-64_W15.24mm_SMDSocket_SmallPads +Package_DIP:DIP-64_W15.24mm_Socket +Package_DIP:DIP-64_W15.24mm_Socket_LongPads +Package_DIP:DIP-64_W16.51mm_SMDSocket_LongPads +Package_DIP:DIP-64_W22.86mm +Package_DIP:DIP-64_W22.86mm_LongPads +Package_DIP:DIP-64_W22.86mm_SMDSocket_SmallPads +Package_DIP:DIP-64_W22.86mm_Socket +Package_DIP:DIP-64_W22.86mm_Socket_LongPads +Package_DIP:DIP-64_W24.13mm_SMDSocket_LongPads +Package_DIP:DIP-64_W25.4mm +Package_DIP:DIP-64_W25.4mm_LongPads +Package_DIP:DIP-64_W25.4mm_SMDSocket_SmallPads +Package_DIP:DIP-64_W25.4mm_Socket +Package_DIP:DIP-64_W25.4mm_Socket_LongPads +Package_DIP:DIP-64_W26.67mm_SMDSocket_LongPads +Package_DIP:DIP-6_W10.16mm +Package_DIP:DIP-6_W10.16mm_LongPads +Package_DIP:DIP-6_W7.62mm +Package_DIP:DIP-6_W7.62mm_LongPads +Package_DIP:DIP-6_W7.62mm_SMDSocket_SmallPads +Package_DIP:DIP-6_W7.62mm_Socket +Package_DIP:DIP-6_W7.62mm_Socket_LongPads +Package_DIP:DIP-6_W8.89mm_SMDSocket_LongPads +Package_DIP:DIP-8-16_W7.62mm +Package_DIP:DIP-8-16_W7.62mm_Socket +Package_DIP:DIP-8-16_W7.62mm_Socket_LongPads +Package_DIP:DIP-8-N6_W7.62mm +Package_DIP:DIP-8-N7_W7.62mm +Package_DIP:DIP-8_W10.16mm +Package_DIP:DIP-8_W10.16mm_LongPads +Package_DIP:DIP-8_W7.62mm +Package_DIP:DIP-8_W7.62mm_LongPads +Package_DIP:DIP-8_W7.62mm_SMDSocket_SmallPads +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 +Package_DIP:PowerIntegrations_SDIP-10C +Package_DIP:PowerIntegrations_SMD-8 +Package_DIP:PowerIntegrations_SMD-8B +Package_DIP:PowerIntegrations_SMD-8C +Package_DIP:SMDIP-10_W11.48mm +Package_DIP:SMDIP-10_W7.62mm +Package_DIP:SMDIP-10_W9.53mm +Package_DIP:SMDIP-10_W9.53mm_Clearance8mm +Package_DIP:SMDIP-12_W11.48mm +Package_DIP:SMDIP-12_W7.62mm +Package_DIP:SMDIP-12_W9.53mm +Package_DIP:SMDIP-12_W9.53mm_Clearance8mm +Package_DIP:SMDIP-14_W11.48mm +Package_DIP:SMDIP-14_W7.62mm +Package_DIP:SMDIP-14_W9.53mm +Package_DIP:SMDIP-14_W9.53mm_Clearance8mm +Package_DIP:SMDIP-16_W11.48mm +Package_DIP:SMDIP-16_W7.62mm +Package_DIP:SMDIP-16_W9.53mm +Package_DIP:SMDIP-16_W9.53mm_Clearance8mm +Package_DIP:SMDIP-18_W11.48mm +Package_DIP:SMDIP-18_W7.62mm +Package_DIP:SMDIP-18_W9.53mm +Package_DIP:SMDIP-18_W9.53mm_Clearance8mm +Package_DIP:SMDIP-20_W11.48mm +Package_DIP:SMDIP-20_W7.62mm +Package_DIP:SMDIP-20_W9.53mm +Package_DIP:SMDIP-20_W9.53mm_Clearance8mm +Package_DIP:SMDIP-22_W11.48mm +Package_DIP:SMDIP-22_W7.62mm +Package_DIP:SMDIP-22_W9.53mm +Package_DIP:SMDIP-22_W9.53mm_Clearance8mm +Package_DIP:SMDIP-24_W11.48mm +Package_DIP:SMDIP-24_W15.24mm +Package_DIP:SMDIP-24_W7.62mm +Package_DIP:SMDIP-24_W9.53mm +Package_DIP:SMDIP-28_W15.24mm +Package_DIP:SMDIP-32_W11.48mm +Package_DIP:SMDIP-32_W15.24mm +Package_DIP:SMDIP-32_W7.62mm +Package_DIP:SMDIP-32_W9.53mm +Package_DIP:SMDIP-40_W15.24mm +Package_DIP:SMDIP-40_W25.24mm +Package_DIP:SMDIP-42_W15.24mm +Package_DIP:SMDIP-48_W15.24mm +Package_DIP:SMDIP-4_W11.48mm +Package_DIP:SMDIP-4_W7.62mm +Package_DIP:SMDIP-4_W9.53mm +Package_DIP:SMDIP-4_W9.53mm_Clearance8mm +Package_DIP:SMDIP-64_W15.24mm +Package_DIP:SMDIP-6_W11.48mm +Package_DIP:SMDIP-6_W7.62mm +Package_DIP:SMDIP-6_W9.53mm +Package_DIP:SMDIP-6_W9.53mm_Clearance8mm +Package_DIP:SMDIP-8_W11.48mm +Package_DIP:SMDIP-8_W7.62mm +Package_DIP:SMDIP-8_W9.53mm +Package_DIP:SMDIP-8_W9.53mm_Clearance8mm +Package_DIP:Toshiba_11-7A9 +Package_DIP:Vishay_HVM-DIP-3_W7.62mm +Package_DirectFET:DirectFET_L4 +Package_DirectFET:DirectFET_L6 +Package_DirectFET:DirectFET_L8 +Package_DirectFET:DirectFET_LA +Package_DirectFET:DirectFET_M2 +Package_DirectFET:DirectFET_M4 +Package_DirectFET:DirectFET_MA +Package_DirectFET:DirectFET_MB +Package_DirectFET:DirectFET_MC +Package_DirectFET:DirectFET_MD +Package_DirectFET:DirectFET_ME +Package_DirectFET:DirectFET_MF +Package_DirectFET:DirectFET_MN +Package_DirectFET:DirectFET_MP +Package_DirectFET:DirectFET_MQ +Package_DirectFET:DirectFET_MT +Package_DirectFET:DirectFET_MU +Package_DirectFET:DirectFET_MX +Package_DirectFET:DirectFET_MZ +Package_DirectFET:DirectFET_S1 +Package_DirectFET:DirectFET_S2 +Package_DirectFET:DirectFET_S3C +Package_DirectFET:DirectFET_SA +Package_DirectFET:DirectFET_SB +Package_DirectFET:DirectFET_SC +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 +Package_LCC:PLCC-28 +Package_LCC:PLCC-28_SMD-Socket +Package_LCC:PLCC-28_THT-Socket +Package_LCC:PLCC-32_11.4x14.0mm_P1.27mm +Package_LCC:PLCC-32_THT-Socket +Package_LCC:PLCC-44 +Package_LCC:PLCC-44_16.6x16.6mm_P1.27mm +Package_LCC:PLCC-44_SMD-Socket +Package_LCC:PLCC-44_THT-Socket +Package_LCC:PLCC-52 +Package_LCC:PLCC-52_SMD-Socket +Package_LCC:PLCC-52_THT-Socket +Package_LCC:PLCC-68 +Package_LCC:PLCC-68_24.2x24.2mm_P1.27mm +Package_LCC:PLCC-68_SMD-Socket +Package_LCC:PLCC-68_THT-Socket +Package_LCC:PLCC-84 +Package_LCC:PLCC-84_29.3x29.3mm_P1.27mm +Package_LCC:PLCC-84_SMD-Socket +Package_LCC:PLCC-84_THT-Socket +Package_LGA:AMS_LGA-10-1EP_2.7x4mm_P0.6mm +Package_LGA:AMS_LGA-20_4.7x4.5mm_P0.65mm +Package_LGA:AMS_OLGA-8_2x3.1mm_P0.8mm +Package_LGA:Bosch_LGA-14_3x2.5mm_P0.5mm +Package_LGA:Bosch_LGA-8_2.5x2.5mm_P0.65mm_ClockwisePinNumbering +Package_LGA:Bosch_LGA-8_2x2.5mm_P0.65mm_ClockwisePinNumbering +Package_LGA:Bosch_LGA-8_3x3mm_P0.8mm_ClockwisePinNumbering +Package_LGA:Infineon_PG-TSNP-6-10_0.7x1.1mm_0.7x1.1mm_P0.4mm +Package_LGA:Kionix_LGA-12_2x2mm_P0.5mm_LayoutBorder2x4y +Package_LGA:LGA-12_2x2mm_P0.5mm +Package_LGA:LGA-14_2x2mm_P0.35mm_LayoutBorder3x4y +Package_LGA:LGA-14_3x2.5mm_P0.5mm_LayoutBorder3x4y +Package_LGA:LGA-14_3x5mm_P0.8mm_LayoutBorder1x6y +Package_LGA:LGA-16_3x3mm_P0.5mm +Package_LGA:LGA-16_3x3mm_P0.5mm_LayoutBorder3x5y +Package_LGA:LGA-16_4x4mm_P0.65mm_LayoutBorder4x4y +Package_LGA:LGA-24L_3x3.5mm_P0.43mm +Package_LGA:LGA-28_5.2x3.8mm_P0.5mm +Package_LGA:LGA-8_3x5mm_P1.25mm +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_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 +Package_LGA:Texas_SIL0008D_MicroSiP-8-1EP_2.8x3mm_P0.65mm_EP1.1x1.9mm_ThermalVias +Package_LGA:Texas_SIL0010A_MicroSiP-10-1EP_3.8x3mm_P0.6mm_EP0.7x2.9mm +Package_LGA:Texas_SIL0010A_MicroSiP-10-1EP_3.8x3mm_P0.6mm_EP0.7x2.9mm_ThermalVias +Package_LGA:VLGA-4_2x2.5mm_P1.65mm +Package_QFP:EQFP-144-1EP_20x20mm_P0.5mm_EP4x4mm +Package_QFP:EQFP-144-1EP_20x20mm_P0.5mm_EP4x4mm_ThermalVias +Package_QFP:EQFP-144-1EP_20x20mm_P0.5mm_EP5x5mm +Package_QFP:EQFP-144-1EP_20x20mm_P0.5mm_EP5x5mm_ThermalVias +Package_QFP:EQFP-144-1EP_20x20mm_P0.5mm_EP6.61x5.615mm +Package_QFP:EQFP-144-1EP_20x20mm_P0.5mm_EP6.61x5.615mm_ThermalVias +Package_QFP:EQFP-144-1EP_20x20mm_P0.5mm_EP7.2x6.35mm +Package_QFP:EQFP-144-1EP_20x20mm_P0.5mm_EP7.2x6.35mm_ThermalVias +Package_QFP:EQFP-144-1EP_20x20mm_P0.5mm_EP8.93x8.7mm +Package_QFP:EQFP-144-1EP_20x20mm_P0.5mm_EP8.93x8.7mm_ThermalVias +Package_QFP:HTQFP-64-1EP_10x10mm_P0.5mm_EP8x8mm +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 +Package_QFP:LQFP-176_24x24mm_P0.5mm +Package_QFP:LQFP-208_28x28mm_P0.5mm +Package_QFP:LQFP-216_24x24mm_P0.4mm +Package_QFP:LQFP-32_5x5mm_P0.5mm +Package_QFP:LQFP-32_7x7mm_P0.8mm +Package_QFP:LQFP-36_7x7mm_P0.65mm +Package_QFP:LQFP-44_10x10mm_P0.8mm +Package_QFP:LQFP-48-1EP_7x7mm_P0.5mm_EP3.6x3.6mm +Package_QFP:LQFP-48-1EP_7x7mm_P0.5mm_EP3.6x3.6mm_ThermalVias +Package_QFP:LQFP-48_7x7mm_P0.5mm +Package_QFP:LQFP-52-1EP_10x10mm_P0.65mm_EP4.8x4.8mm +Package_QFP:LQFP-52-1EP_10x10mm_P0.65mm_EP4.8x4.8mm_ThermalVias +Package_QFP:LQFP-52_10x10mm_P0.65mm +Package_QFP:LQFP-52_14x14mm_P1mm +Package_QFP:LQFP-64-1EP_10x10mm_P0.5mm_EP5x5mm +Package_QFP:LQFP-64-1EP_10x10mm_P0.5mm_EP5x5mm_ThermalVias +Package_QFP:LQFP-64-1EP_10x10mm_P0.5mm_EP6.5x6.5mm +Package_QFP:LQFP-64-1EP_10x10mm_P0.5mm_EP6.5x6.5mm_ThermalVias +Package_QFP:LQFP-64_10x10mm_P0.5mm +Package_QFP:LQFP-64_14x14mm_P0.8mm +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 +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 +Package_QFP:TQFP-100-1EP_14x14mm_P0.5mm_EP5x5mm +Package_QFP:TQFP-100-1EP_14x14mm_P0.5mm_EP5x5mm_ThermalVias +Package_QFP:TQFP-100_12x12mm_P0.4mm +Package_QFP:TQFP-100_14x14mm_P0.5mm +Package_QFP:TQFP-120_14x14mm_P0.4mm +Package_QFP:TQFP-128_14x14mm_P0.4mm +Package_QFP:TQFP-144_16x16mm_P0.4mm +Package_QFP:TQFP-144_20x20mm_P0.5mm +Package_QFP:TQFP-176_24x24mm_P0.5mm +Package_QFP:TQFP-32_7x7mm_P0.8mm +Package_QFP:TQFP-44-1EP_10x10mm_P0.8mm_EP4.5x4.5mm +Package_QFP:TQFP-44_10x10mm_P0.8mm +Package_QFP:TQFP-48-1EP_7x7mm_P0.5mm_EP3.5x3.5mm +Package_QFP:TQFP-48-1EP_7x7mm_P0.5mm_EP4.11x4.11mm +Package_QFP:TQFP-48-1EP_7x7mm_P0.5mm_EP5x5mm +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 +Package_QFP:TQFP-64_7x7mm_P0.4mm +Package_QFP:TQFP-80-1EP_14x14mm_P0.65mm_EP9.5x9.5mm +Package_QFP:TQFP-80_12x12mm_P0.5mm +Package_QFP:TQFP-80_14x14mm_P0.65mm +Package_QFP:VQFP-100_14x14mm_P0.5mm +Package_QFP:VQFP-128_14x14mm_P0.4mm +Package_QFP:VQFP-176_20x20mm_P0.4mm +Package_QFP:VQFP-80_14x14mm_P0.65mm +Package_SIP:PowerIntegrations_eSIP-7C +Package_SIP:PowerIntegrations_eSIP-7F +Package_SIP:Sanyo_STK4xx-15_59.2x8.0mm_P2.54mm +Package_SIP:Sanyo_STK4xx-15_78.0x8.0mm_P2.54mm +Package_SIP:SIP-8_19x3mm_P2.54mm +Package_SIP:SIP-9_21.54x3mm_P2.54mm +Package_SIP:SIP-9_22.3x3mm_P2.54mm +Package_SIP:SIP3_11.6x8.5mm +Package_SIP:SIP4_Sharp-SSR_P7.62mm_Angled +Package_SIP:SIP4_Sharp-SSR_P7.62mm_Angled_NoHole +Package_SIP:SIP4_Sharp-SSR_P7.62mm_Straight +Package_SIP:SIP9_Housing +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 +Package_SO:HSOP-20-1EP_11.0x15.9mm_P1.27mm_SlugDown +Package_SO:HSOP-20-1EP_11.0x15.9mm_P1.27mm_SlugDown_ThermalVias +Package_SO:HSOP-20-1EP_11.0x15.9mm_P1.27mm_SlugUp +Package_SO:HSOP-32-1EP_7.5x11mm_P0.65mm_EP4.7x4.7mm +Package_SO:HSOP-36-1EP_11.0x15.9mm_P0.65mm_SlugDown +Package_SO:HSOP-36-1EP_11.0x15.9mm_P0.65mm_SlugDown_ThermalVias +Package_SO:HSOP-36-1EP_11.0x15.9mm_P0.65mm_SlugUp +Package_SO:HSOP-54-1EP_7.5x17.9mm_P0.65mm_EP4.6x4.6mm +Package_SO:HSOP-8-1EP_3.9x4.9mm_P1.27mm_EP2.3x2.3mm +Package_SO:HSOP-8-1EP_3.9x4.9mm_P1.27mm_EP2.3x2.3mm_ThermalVias +Package_SO:HSOP-8-1EP_3.9x4.9mm_P1.27mm_EP2.41x3.1mm +Package_SO:HSOP-8-1EP_3.9x4.9mm_P1.27mm_EP2.41x3.1mm_ThermalVias +Package_SO:HTSOP-8-1EP_3.9x4.9mm_P1.27mm_EP2.4x3.2mm +Package_SO:HTSOP-8-1EP_3.9x4.9mm_P1.27mm_EP2.4x3.2mm_ThermalVias +Package_SO:HTSSOP-14-1EP_4.4x5mm_P0.65mm_EP3.4x5mm_Mask3x3.1mm +Package_SO:HTSSOP-14-1EP_4.4x5mm_P0.65mm_EP3.4x5mm_Mask3x3.1mm_ThermalVias +Package_SO:HTSSOP-16-1EP_4.4x5mm_P0.65mm_EP3.4x5mm +Package_SO:HTSSOP-16-1EP_4.4x5mm_P0.65mm_EP3.4x5mm_Mask2.46x2.31mm +Package_SO:HTSSOP-16-1EP_4.4x5mm_P0.65mm_EP3.4x5mm_Mask2.46x2.31mm_ThermalVias +Package_SO:HTSSOP-16-1EP_4.4x5mm_P0.65mm_EP3.4x5mm_Mask3x3mm_ThermalVias +Package_SO:HTSSOP-16-1EP_4.4x5mm_P0.65mm_EP3x3mm +Package_SO:HTSSOP-20-1EP_4.4x6.5mm_P0.65mm_EP2.74x3.86mm +Package_SO:HTSSOP-20-1EP_4.4x6.5mm_P0.65mm_EP2.85x4mm +Package_SO:HTSSOP-20-1EP_4.4x6.5mm_P0.65mm_EP3.4x6.5mm +Package_SO:HTSSOP-20-1EP_4.4x6.5mm_P0.65mm_EP3.4x6.5mm_Mask2.4x3.7mm +Package_SO:HTSSOP-20-1EP_4.4x6.5mm_P0.65mm_EP3.4x6.5mm_Mask2.75x3.43mm +Package_SO:HTSSOP-20-1EP_4.4x6.5mm_P0.65mm_EP3.4x6.5mm_Mask2.75x3.43mm_ThermalVias +Package_SO:HTSSOP-20-1EP_4.4x6.5mm_P0.65mm_EP3.4x6.5mm_Mask2.75x3.43mm_ThermalVias_HandSolder +Package_SO:HTSSOP-20-1EP_4.4x6.5mm_P0.65mm_EP3.4x6.5mm_Mask2.96x2.96mm +Package_SO:HTSSOP-20-1EP_4.4x6.5mm_P0.65mm_EP3.4x6.5mm_Mask2.96x2.96mm_ThermalVias +Package_SO:HTSSOP-20-1EP_4.4x6.5mm_P0.65mm_EP3.4x6.5mm_ThermalVias +Package_SO:HTSSOP-24-1EP_4.4x7.8mm_P0.65mm_EP3.2x5mm +Package_SO:HTSSOP-24-1EP_4.4x7.8mm_P0.65mm_EP3.4x7.8mm_Mask2.4x2.98mm +Package_SO:HTSSOP-24-1EP_4.4x7.8mm_P0.65mm_EP3.4x7.8mm_Mask2.4x2.98mm_ThermalVias +Package_SO:HTSSOP-24-1EP_4.4x7.8mm_P0.65mm_EP3.4x7.8mm_Mask2.4x4.68mm +Package_SO:HTSSOP-24-1EP_4.4x7.8mm_P0.65mm_EP3.4x7.8mm_Mask2.4x4.68mm_ThermalVias +Package_SO:HTSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP2.75x6.2mm +Package_SO:HTSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP2.75x6.2mm_ThermalVias +Package_SO:HTSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP2.85x5.4mm +Package_SO:HTSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP2.85x5.4mm_ThermalVias +Package_SO:HTSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP3.4x9.5mm +Package_SO:HTSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP3.4x9.5mm_Mask2.4x6.17mm +Package_SO:HTSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP3.4x9.5mm_Mask2.4x6.17mm_ThermalVias +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 +Package_SO:HVSSOP-10-1EP_3x3mm_P0.5mm_EP1.57x1.88mm_ThermalVias +Package_SO:HVSSOP-8-1EP_3x3mm_P0.65mm_EP1.57x1.89mm +Package_SO:HVSSOP-8-1EP_3x3mm_P0.65mm_EP1.57x1.89mm_ThermalVias +Package_SO:Infineon_PG-DSO-12-11 +Package_SO:Infineon_PG-DSO-12-11_ThermalVias +Package_SO:Infineon_PG-DSO-12-9 +Package_SO:Infineon_PG-DSO-12-9_ThermalVias +Package_SO:Infineon_PG-DSO-20-30 +Package_SO:Infineon_PG-DSO-20-30_ThermalVias +Package_SO:Infineon_PG-DSO-20-32 +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: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 +Package_SO:MSOP-10-1EP_3x3mm_P0.5mm_EP1.68x1.88mm_ThermalVias +Package_SO:MSOP-10-1EP_3x3mm_P0.5mm_EP1.73x1.98mm +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-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_3x4.039mm_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 +Package_SO:MSOP-8-1EP_3x3mm_P0.65mm_EP1.68x1.88mm_ThermalVias +Package_SO:MSOP-8-1EP_3x3mm_P0.65mm_EP1.73x1.85mm +Package_SO:MSOP-8-1EP_3x3mm_P0.65mm_EP1.73x1.85mm_ThermalVias +Package_SO:MSOP-8-1EP_3x3mm_P0.65mm_EP1.95x2.15mm +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 +Package_SO:PowerIntegrations_SO-8 +Package_SO:PowerIntegrations_SO-8B +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: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 +Package_SO:SO-16_5.3x10.2mm_P1.27mm +Package_SO:SO-20-1EP_7.52x12.825mm_P1.27mm_EP6.045x12.09mm_Mask3.56x4.47mm +Package_SO:SO-20-1EP_7.52x12.825mm_P1.27mm_EP6.045x12.09mm_Mask3.56x4.47mm_ThermalVias +Package_SO:SO-20_12.8x7.5mm_P1.27mm +Package_SO:SO-20_5.3x12.6mm_P1.27mm +Package_SO:SO-24_5.3x15mm_P1.27mm +Package_SO:SO-4_4.4x2.3mm_P1.27mm +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: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 +Package_SO:SOIC-16W-12_7.5x10.3mm_P1.27mm +Package_SO:SOIC-16W_5.3x10.2mm_P1.27mm +Package_SO:SOIC-16W_7.5x10.3mm_P1.27mm +Package_SO:SOIC-16W_7.5x12.8mm_P1.27mm +Package_SO:SOIC-16_3.9x9.9mm_P1.27mm +Package_SO:SOIC-16_4.55x10.3mm_P1.27mm +Package_SO:SOIC-18W_7.5x11.6mm_P1.27mm +Package_SO:SOIC-20W_7.5x12.8mm_P1.27mm +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 +Package_SO:SOIC-8-1EP_3.9x4.9mm_P1.27mm_EP2.29x3mm_ThermalVias +Package_SO:SOIC-8-1EP_3.9x4.9mm_P1.27mm_EP2.41x3.3mm +Package_SO:SOIC-8-1EP_3.9x4.9mm_P1.27mm_EP2.41x3.3mm_ThermalVias +Package_SO:SOIC-8-1EP_3.9x4.9mm_P1.27mm_EP2.41x3.81mm +Package_SO:SOIC-8-1EP_3.9x4.9mm_P1.27mm_EP2.41x3.81mm_ThermalVias +Package_SO:SOIC-8-1EP_3.9x4.9mm_P1.27mm_EP2.514x3.2mm +Package_SO:SOIC-8-1EP_3.9x4.9mm_P1.27mm_EP2.514x3.2mm_ThermalVias +Package_SO:SOIC-8-1EP_3.9x4.9mm_P1.27mm_EP2.62x3.51mm +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-8_3.9x4.9mm_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-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_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 +Package_SO:SSOP-16_4.4x5.2mm_P0.65mm +Package_SO:SSOP-16_5.3x6.2mm_P0.65mm +Package_SO:SSOP-18_4.4x6.5mm_P0.65mm +Package_SO:SSOP-20_3.9x8.7mm_P0.635mm +Package_SO:SSOP-20_4.4x6.5mm_P0.65mm +Package_SO:SSOP-20_5.3x7.2mm_P0.65mm +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-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 +Package_SO:SSOP-8_3.9x5.05mm_P1.27mm +Package_SO:SSOP-8_5.25x5.24mm_P1.27mm +Package_SO:STC_SOP-16_3.9x9.9mm_P1.27mm +Package_SO:ST_MultiPowerSO-30 +Package_SO:ST_PowerSSO-24_SlugDown +Package_SO:ST_PowerSSO-24_SlugDown_ThermalVias +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_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 +Package_SO:TSOP-6_1.65x3.05mm_P0.95mm +Package_SO:TSOP-I-24_12.4x6mm_P0.5mm +Package_SO:TSOP-I-24_14.4x6mm_P0.5mm +Package_SO:TSOP-I-24_16.4x6mm_P0.5mm +Package_SO:TSOP-I-24_18.4x6mm_P0.5mm +Package_SO:TSOP-I-28_11.8x8mm_P0.55mm +Package_SO:TSOP-I-32_11.8x8mm_P0.5mm +Package_SO:TSOP-I-32_12.4x8mm_P0.5mm +Package_SO:TSOP-I-32_14.4x8mm_P0.5mm +Package_SO:TSOP-I-32_16.4x8mm_P0.5mm +Package_SO:TSOP-I-32_18.4x8mm_P0.5mm +Package_SO:TSOP-I-32_18.4x8mm_P0.5mm_Reverse +Package_SO:TSOP-I-40_12.4x10mm_P0.5mm +Package_SO:TSOP-I-40_14.4x10mm_P0.5mm +Package_SO:TSOP-I-40_16.4x10mm_P0.5mm +Package_SO:TSOP-I-40_18.4x10mm_P0.5mm +Package_SO:TSOP-I-48_12.4x12mm_P0.5mm +Package_SO:TSOP-I-48_14.4x12mm_P0.5mm +Package_SO:TSOP-I-48_16.4x12mm_P0.5mm +Package_SO:TSOP-I-48_18.4x12mm_P0.5mm +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 +Package_SO:TSSOP-10_3x3mm_P0.5mm +Package_SO:TSSOP-14-1EP_4.4x5mm_P0.65mm +Package_SO:TSSOP-14_4.4x3.6mm_P0.4mm +Package_SO:TSSOP-14_4.4x5mm_P0.65mm +Package_SO:TSSOP-16-1EP_4.4x5mm_P0.65mm +Package_SO:TSSOP-16-1EP_4.4x5mm_P0.65mm_EP3x3mm +Package_SO:TSSOP-16-1EP_4.4x5mm_P0.65mm_EP3x3mm_ThermalVias +Package_SO:TSSOP-16_4.4x3.6mm_P0.4mm +Package_SO:TSSOP-16_4.4x5mm_P0.65mm +Package_SO:TSSOP-20-1EP_4.4x6.5mm_P0.65mm_EP2.15x3.35mm +Package_SO:TSSOP-20_4.4x5mm_P0.4mm +Package_SO:TSSOP-20_4.4x5mm_P0.5mm +Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm +Package_SO:TSSOP-24-1EP_4.4x7.8mm_P0.65mm_EP3.2x5mm +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_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 +Package_SO:TSSOP-28_6.1x9.7mm_P0.65mm +Package_SO:TSSOP-28_8x9.7mm_P0.65mm +Package_SO:TSSOP-30_4.4x7.8mm_P0.5mm +Package_SO:TSSOP-30_6.1x9.7mm_P0.65mm +Package_SO:TSSOP-32_4.4x6.5mm_P0.4mm +Package_SO:TSSOP-32_6.1x11mm_P0.65mm +Package_SO:TSSOP-32_8x11mm_P0.65mm +Package_SO:TSSOP-36_4.4x7.8mm_P0.4mm +Package_SO:TSSOP-36_4.4x9.7mm_P0.5mm +Package_SO:TSSOP-36_6.1x12.5mm_P0.65mm +Package_SO:TSSOP-36_6.1x7.8mm_P0.4mm +Package_SO:TSSOP-36_6.1x9.7mm_P0.5mm +Package_SO:TSSOP-36_8x12.5mm_P0.65mm +Package_SO:TSSOP-36_8x9.7mm_P0.5mm +Package_SO:TSSOP-38_4.4x9.7mm_P0.5mm +Package_SO:TSSOP-38_6.1x12.5mm_P0.65mm +Package_SO:TSSOP-40_6.1x11mm_P0.5mm +Package_SO:TSSOP-40_6.1x14mm_P0.65mm +Package_SO:TSSOP-40_8x11mm_P0.5mm +Package_SO:TSSOP-40_8x14mm_P0.65mm +Package_SO:TSSOP-44_4.4x11.2mm_P0.5mm +Package_SO:TSSOP-44_4.4x11mm_P0.5mm +Package_SO:TSSOP-44_6.1x11mm_P0.5mm +Package_SO:TSSOP-48_4.4x9.7mm_P0.4mm +Package_SO:TSSOP-48_6.1x12.5mm_P0.5mm +Package_SO:TSSOP-48_6.1x9.7mm_P0.4mm +Package_SO:TSSOP-48_8x12.5mm_P0.5mm +Package_SO:TSSOP-48_8x9.7mm_P0.4mm +Package_SO:TSSOP-4_4.4x5mm_P4mm +Package_SO:TSSOP-50_4.4x12.5mm_P0.5mm +Package_SO:TSSOP-52_6.1x11mm_P0.4mm +Package_SO:TSSOP-52_8x11mm_P0.4mm +Package_SO:TSSOP-56_4.4x11.3mm_P0.4mm +Package_SO:TSSOP-56_6.1x12.5mm_P0.4mm +Package_SO:TSSOP-56_6.1x14mm_P0.5mm +Package_SO:TSSOP-56_8x12.5mm_P0.4mm +Package_SO:TSSOP-56_8x14mm_P0.5mm +Package_SO:TSSOP-60_8x12.5mm_P0.4mm +Package_SO:TSSOP-64_6.1x14mm_P0.4mm +Package_SO:TSSOP-64_6.1x17mm_P0.5mm +Package_SO:TSSOP-64_8x14mm_P0.4mm +Package_SO:TSSOP-68_8x14mm_P0.4mm +Package_SO:TSSOP-80_6.1x17mm_P0.4mm +Package_SO:TSSOP-8_3x3mm_P0.65mm +Package_SO:TSSOP-8_4.4x3mm_P0.65mm +Package_SO:Vishay_PowerPAK_1212-8_Dual +Package_SO:Vishay_PowerPAK_1212-8_Single +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_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 +Package_SON:HUSON-3-1EP_2x2mm_P1.3mm_EP1.1x1.6mm +Package_SON:HVSON-8-1EP_3x3mm_P0.65mm_EP1.6x2.4mm +Package_SON:HVSON-8-1EP_4x4mm_P0.8mm_EP2.2x3.1mm +Package_SON:Infineon_PG-LSON-8-1 +Package_SON:Infineon_PG-TDSON-8_6.15x5.15mm +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 +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 +Package_SON:Texas_R-PUSON-N14 +Package_SON:Texas_R-PUSON-N8_USON-8-1EP_1.6x2.1mm_P0.5mm_EP0.4x1.7mm +Package_SON:Texas_R-PWSON-N12_EP0.4x2mm +Package_SON:Texas_S-PDSO-N12 +Package_SON:Texas_S-PVSON-N10 +Package_SON:Texas_S-PVSON-N10_ThermalVias +Package_SON:Texas_S-PVSON-N8 +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 +Package_SON:USON-10_2.5x1.0mm_P0.5mm +Package_SON:USON-20_2x4mm_P0.4mm +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 +Package_SON:WSON-16_3.3x1.35_P0.4mm +Package_SON:WSON-6-1EP_2x2mm_P0.65mm_EP1x1.6mm +Package_SON:WSON-6-1EP_2x2mm_P0.65mm_EP1x1.6mm_ThermalVias +Package_SON:WSON-6-1EP_3x3mm_P0.95mm +Package_SON:WSON-6_1.5x1.5mm_P0.5mm +Package_SON:WSON-8-1EP_2x2mm_P0.5mm_EP0.9x1.6mm +Package_SON:WSON-8-1EP_2x2mm_P0.5mm_EP0.9x1.6mm_ThermalVias +Package_SON:WSON-8-1EP_3x2.5mm_P0.5mm_EP1.2x1.5mm_PullBack +Package_SON:WSON-8-1EP_3x2.5mm_P0.5mm_EP1.2x1.5mm_PullBack_ThermalVias +Package_SON:WSON-8-1EP_3x3mm_P0.5mm_EP1.2x2mm +Package_SON:WSON-8-1EP_3x3mm_P0.5mm_EP1.2x2mm_ThermalVias +Package_SON:WSON-8-1EP_3x3mm_P0.5mm_EP1.45x2.4mm +Package_SON:WSON-8-1EP_3x3mm_P0.5mm_EP1.45x2.4mm_ThermalVias +Package_SON:WSON-8-1EP_3x3mm_P0.5mm_EP1.6x2.0mm +Package_SON:WSON-8-1EP_4x4mm_P0.8mm_EP1.98x3mm +Package_SON:WSON-8-1EP_4x4mm_P0.8mm_EP1.98x3mm_ThermalVias +Package_SON:WSON-8-1EP_4x4mm_P0.8mm_EP2.2x3mm +Package_SON:WSON-8-1EP_4x4mm_P0.8mm_EP2.2x3mm_ThermalVias +Package_SON:WSON-8-1EP_4x4mm_P0.8mm_EP2.6x3mm +Package_SON:WSON-8-1EP_4x4mm_P0.8mm_EP2.6x3mm_ThermalVias +Package_SON:WSON-8-1EP_6x5mm_P1.27mm_EP3.4x4.3mm +Package_SON:WSON-8-1EP_6x5mm_P1.27mm_EP3.4x4mm +Package_SON:WSON-8-1EP_8x6mm_P1.27mm_EP3.4x4.3mm +Package_SON:X2SON-8_1.4x1mm_P0.35mm +Package_SO_J-Lead:TSOC-6_3.76x3.94mm_P1.27mm +Package_TO_SOT_SMD:Analog_KS-4 +Package_TO_SOT_SMD:ATPAK-2 +Package_TO_SOT_SMD:Diodes_SOT-553 +Package_TO_SOT_SMD:HVSOF5 +Package_TO_SOT_SMD:HVSOF6 +Package_TO_SOT_SMD:Infineon_PG-HDSOP-10-1 +Package_TO_SOT_SMD:Infineon_PG-HSOF-8-1 +Package_TO_SOT_SMD:Infineon_PG-HSOF-8-1_ThermalVias +Package_TO_SOT_SMD:Infineon_PG-HSOF-8-2 +Package_TO_SOT_SMD:Infineon_PG-HSOF-8-2_ThermalVias +Package_TO_SOT_SMD:Infineon_PG-HSOF-8-2_ThermalVias2 +Package_TO_SOT_SMD:Infineon_PG-HSOF-8-3 +Package_TO_SOT_SMD:Infineon_PG-HSOF-8-3_ThermalVias +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 +Package_TO_SOT_SMD:SC-82AB_Handsoldering +Package_TO_SOT_SMD:SOT-1123 +Package_TO_SOT_SMD:SOT-1333-1 +Package_TO_SOT_SMD:SOT-1334-1 +Package_TO_SOT_SMD:SOT-143 +Package_TO_SOT_SMD:SOT-143R +Package_TO_SOT_SMD:SOT-143R_Handsoldering +Package_TO_SOT_SMD:SOT-143_Handsoldering +Package_TO_SOT_SMD:SOT-223-3_TabPin2 +Package_TO_SOT_SMD:SOT-223-5 +Package_TO_SOT_SMD:SOT-223-6 +Package_TO_SOT_SMD:SOT-223-6_TabPin3 +Package_TO_SOT_SMD:SOT-223-8 +Package_TO_SOT_SMD:SOT-223 +Package_TO_SOT_SMD:SOT-23-3 +Package_TO_SOT_SMD:SOT-23-5 +Package_TO_SOT_SMD:SOT-23-5_HandSoldering +Package_TO_SOT_SMD:SOT-23-6 +Package_TO_SOT_SMD:SOT-23-6_Handsoldering +Package_TO_SOT_SMD:SOT-23-8 +Package_TO_SOT_SMD:SOT-23-8_Handsoldering +Package_TO_SOT_SMD:SOT-23 +Package_TO_SOT_SMD:SOT-23W +Package_TO_SOT_SMD:SOT-23W_Handsoldering +Package_TO_SOT_SMD:SOT-23_Handsoldering +Package_TO_SOT_SMD:SOT-323_SC-70 +Package_TO_SOT_SMD:SOT-323_SC-70_Handsoldering +Package_TO_SOT_SMD:SOT-343_SC-70-4 +Package_TO_SOT_SMD:SOT-343_SC-70-4_Handsoldering +Package_TO_SOT_SMD:SOT-353_SC-70-5 +Package_TO_SOT_SMD:SOT-353_SC-70-5_Handsoldering +Package_TO_SOT_SMD:SOT-363_SC-70-6 +Package_TO_SOT_SMD:SOT-363_SC-70-6_Handsoldering +Package_TO_SOT_SMD:SOT-383F +Package_TO_SOT_SMD:SOT-383FL +Package_TO_SOT_SMD:SOT-416 +Package_TO_SOT_SMD:SOT-523 +Package_TO_SOT_SMD:SOT-543 +Package_TO_SOT_SMD:SOT-553 +Package_TO_SOT_SMD:SOT-563 +Package_TO_SOT_SMD:SOT-583-8 +Package_TO_SOT_SMD:SOT-665 +Package_TO_SOT_SMD:SOT-666 +Package_TO_SOT_SMD:SOT-723 +Package_TO_SOT_SMD:SOT-883 +Package_TO_SOT_SMD:SOT-886 +Package_TO_SOT_SMD:SOT-89-3 +Package_TO_SOT_SMD:SOT-89-3_Handsoldering +Package_TO_SOT_SMD:SOT-89-5 +Package_TO_SOT_SMD:SOT-89-5_Handsoldering +Package_TO_SOT_SMD:SOT-963 +Package_TO_SOT_SMD:SuperSOT-3 +Package_TO_SOT_SMD:SuperSOT-6 +Package_TO_SOT_SMD:SuperSOT-8 +Package_TO_SOT_SMD:TDSON-8-1 +Package_TO_SOT_SMD:Texas_DRT-3 +Package_TO_SOT_SMD:Texas_NDQ +Package_TO_SOT_SMD:Texas_NDW-7_TabPin4 +Package_TO_SOT_SMD:Texas_NDW-7_TabPin8 +Package_TO_SOT_SMD:Texas_NDY0011A +Package_TO_SOT_SMD:Texas_R-PDSO-G5_DCK-5 +Package_TO_SOT_SMD:Texas_R-PDSO-G6 +Package_TO_SOT_SMD:Texas_R-PDSO-N5_DRL-5 +Package_TO_SOT_SMD:Texas_R-PDSO-N6_DRL-6 +Package_TO_SOT_SMD:TO-252-2 +Package_TO_SOT_SMD:TO-252-2_TabPin1 +Package_TO_SOT_SMD:TO-252-3_TabPin2 +Package_TO_SOT_SMD:TO-252-3_TabPin4 +Package_TO_SOT_SMD:TO-252-4 +Package_TO_SOT_SMD:TO-252-5_TabPin3 +Package_TO_SOT_SMD:TO-252-5_TabPin6 +Package_TO_SOT_SMD:TO-263-2 +Package_TO_SOT_SMD:TO-263-2_TabPin1 +Package_TO_SOT_SMD:TO-263-3_TabPin2 +Package_TO_SOT_SMD:TO-263-3_TabPin4 +Package_TO_SOT_SMD:TO-263-4 +Package_TO_SOT_SMD:TO-263-5_TabPin3 +Package_TO_SOT_SMD:TO-263-5_TabPin6 +Package_TO_SOT_SMD:TO-263-6 +Package_TO_SOT_SMD:TO-263-7_TabPin4 +Package_TO_SOT_SMD:TO-263-7_TabPin8 +Package_TO_SOT_SMD:TO-263-9_TabPin10 +Package_TO_SOT_SMD:TO-263-9_TabPin5 +Package_TO_SOT_SMD:TO-268-2 +Package_TO_SOT_SMD:TO-269AA +Package_TO_SOT_SMD:TO-277A +Package_TO_SOT_SMD:TO-277B +Package_TO_SOT_SMD:TO-50-3_LongPad-NoHole_Housing +Package_TO_SOT_SMD:TO-50-3_LongPad-WithHole_Housing +Package_TO_SOT_SMD:TO-50-3_ShortPad-NoHole_Housing +Package_TO_SOT_SMD:TO-50-3_ShortPad-WithHole_Housing +Package_TO_SOT_SMD:TO-50-4_LongPad-NoHole_Housing +Package_TO_SOT_SMD:TO-50-4_LongPad-WithHole_Housing +Package_TO_SOT_SMD:TO-50-4_ShortPad-NoHole_Housing +Package_TO_SOT_SMD:TO-50-4_ShortPad-WithHole_Housing +Package_TO_SOT_SMD:TSOT-23-5 +Package_TO_SOT_SMD:TSOT-23-5_HandSoldering +Package_TO_SOT_SMD:TSOT-23-6 +Package_TO_SOT_SMD:TSOT-23-6_HandSoldering +Package_TO_SOT_SMD:TSOT-23-8 +Package_TO_SOT_SMD:TSOT-23-8_HandSoldering +Package_TO_SOT_SMD:TSOT-23 +Package_TO_SOT_SMD:TSOT-23_HandSoldering +Package_TO_SOT_SMD:Vishay_PowerPAK_SC70-6L_Dual +Package_TO_SOT_SMD:Vishay_PowerPAK_SC70-6L_Single +Package_TO_SOT_SMD:VSOF5 +Package_TO_SOT_THT:Analog_TO-46-4_ThermalShield +Package_TO_SOT_THT:Fairchild_TO-220F-6L +Package_TO_SOT_THT:Heraeus_TO-92-2 +Package_TO_SOT_THT:NEC_Molded_7x4x9mm +Package_TO_SOT_THT:PowerIntegrations_TO-220-7C +Package_TO_SOT_THT:SIPAK-1EP_Horizontal_TabDown +Package_TO_SOT_THT:SIPAK_Vertical +Package_TO_SOT_THT:SOD-70_P2.54mm +Package_TO_SOT_THT:SOD-70_P5.08mm +Package_TO_SOT_THT:SOT-227 +Package_TO_SOT_THT:TO-100-10 +Package_TO_SOT_THT:TO-100-10_Window +Package_TO_SOT_THT:TO-11-2 +Package_TO_SOT_THT:TO-11-2_Window +Package_TO_SOT_THT:TO-11-3 +Package_TO_SOT_THT:TO-11-3_Window +Package_TO_SOT_THT:TO-12-4 +Package_TO_SOT_THT:TO-12-4_Window +Package_TO_SOT_THT:TO-126-2_Horizontal_TabDown +Package_TO_SOT_THT:TO-126-2_Horizontal_TabUp +Package_TO_SOT_THT:TO-126-2_Vertical +Package_TO_SOT_THT:TO-126-3_Horizontal_TabDown +Package_TO_SOT_THT:TO-126-3_Horizontal_TabUp +Package_TO_SOT_THT:TO-126-3_Vertical +Package_TO_SOT_THT:TO-17-4 +Package_TO_SOT_THT:TO-17-4_Window +Package_TO_SOT_THT:TO-18-2 +Package_TO_SOT_THT:TO-18-2_Lens +Package_TO_SOT_THT:TO-18-2_Window +Package_TO_SOT_THT:TO-18-3 +Package_TO_SOT_THT:TO-18-3_Lens +Package_TO_SOT_THT:TO-18-3_Window +Package_TO_SOT_THT:TO-18-4 +Package_TO_SOT_THT:TO-18-4_Lens +Package_TO_SOT_THT:TO-18-4_Window +Package_TO_SOT_THT:TO-218-2_Horizontal_TabDown +Package_TO_SOT_THT:TO-218-2_Horizontal_TabUp +Package_TO_SOT_THT:TO-218-2_Vertical +Package_TO_SOT_THT:TO-218-3_Horizontal_TabDown +Package_TO_SOT_THT:TO-218-3_Horizontal_TabUp +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.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_Lead5.84mm_TabDown +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 +Package_TO_SOT_THT:TO-220-3_Horizontal_TabDown +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.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 +Package_TO_SOT_THT:TO-220-5_P3.4x3.7mm_StaggerEven_Lead3.8mm_Vertical +Package_TO_SOT_THT:TO-220-5_P3.4x3.7mm_StaggerOdd_Lead3.8mm_Vertical +Package_TO_SOT_THT:TO-220-5_P3.4x3.8mm_StaggerEven_Lead7.13mm_TabDown +Package_TO_SOT_THT:TO-220-5_P3.4x3.8mm_StaggerOdd_Lead7.13mm_TabDown +Package_TO_SOT_THT:TO-220-5_Vertical +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 +Package_TO_SOT_THT:TO-220-9_P1.94x3.8mm_StaggerEven_Lead5.85mm_TabDown +Package_TO_SOT_THT:TO-220-9_P1.94x3.8mm_StaggerOdd_Lead5.85mm_TabDown +Package_TO_SOT_THT:TO-220F-11_P3.4x5.08mm_StaggerEven_Lead5.08mm_Vertical +Package_TO_SOT_THT:TO-220F-11_P3.4x5.08mm_StaggerOdd_Lead5.08mm_Vertical +Package_TO_SOT_THT:TO-220F-15_P2.54x5.08mm_StaggerEven_Lead5.08mm_Vertical +Package_TO_SOT_THT:TO-220F-15_P2.54x5.08mm_StaggerOdd_Lead5.08mm_Vertical +Package_TO_SOT_THT:TO-220F-2_Horizontal_TabDown +Package_TO_SOT_THT:TO-220F-2_Horizontal_TabUp +Package_TO_SOT_THT:TO-220F-2_Vertical +Package_TO_SOT_THT:TO-220F-3_Horizontal_TabDown +Package_TO_SOT_THT:TO-220F-3_Horizontal_TabUp +Package_TO_SOT_THT:TO-220F-3_Vertical +Package_TO_SOT_THT:TO-220F-4_Horizontal_TabDown +Package_TO_SOT_THT:TO-220F-4_Horizontal_TabUp +Package_TO_SOT_THT:TO-220F-4_P5.08x2.05mm_StaggerEven_Lead1.85mm_Vertical +Package_TO_SOT_THT:TO-220F-4_P5.08x2.05mm_StaggerOdd_Lead1.85mm_Vertical +Package_TO_SOT_THT:TO-220F-4_P5.08x3.7mm_StaggerEven_Lead3.5mm_Vertical +Package_TO_SOT_THT:TO-220F-4_P5.08x3.7mm_StaggerOdd_Lead3.5mm_Vertical +Package_TO_SOT_THT:TO-220F-4_Vertical +Package_TO_SOT_THT:TO-220F-5_Horizontal_TabDown +Package_TO_SOT_THT:TO-220F-5_Horizontal_TabUp +Package_TO_SOT_THT:TO-220F-5_P3.4x2.06mm_StaggerEven_Lead1.86mm_Vertical +Package_TO_SOT_THT:TO-220F-5_P3.4x2.06mm_StaggerOdd_Lead1.86mm_Vertical +Package_TO_SOT_THT:TO-220F-5_P3.4x3.7mm_StaggerEven_Lead3.5mm_Vertical +Package_TO_SOT_THT:TO-220F-5_P3.4x3.7mm_StaggerOdd_Lead3.5mm_Vertical +Package_TO_SOT_THT:TO-220F-5_Vertical +Package_TO_SOT_THT:TO-220F-7_P2.54x3.7mm_StaggerEven_Lead3.5mm_Vertical +Package_TO_SOT_THT:TO-220F-7_P2.54x3.7mm_StaggerOdd_Lead3.5mm_Vertical +Package_TO_SOT_THT:TO-220F-9_P1.8x3.7mm_StaggerEven_Lead3.5mm_Vertical +Package_TO_SOT_THT:TO-220F-9_P1.8x3.7mm_StaggerOdd_Lead3.5mm_Vertical +Package_TO_SOT_THT:TO-247-2_Horizontal_TabDown +Package_TO_SOT_THT:TO-247-2_Horizontal_TabUp +Package_TO_SOT_THT:TO-247-2_Vertical +Package_TO_SOT_THT:TO-247-3_Horizontal_TabDown +Package_TO_SOT_THT:TO-247-3_Horizontal_TabUp +Package_TO_SOT_THT:TO-247-3_Vertical +Package_TO_SOT_THT:TO-247-4_Horizontal_TabDown +Package_TO_SOT_THT:TO-247-4_Horizontal_TabUp +Package_TO_SOT_THT:TO-247-4_Vertical +Package_TO_SOT_THT:TO-247-5_Horizontal_TabDown +Package_TO_SOT_THT:TO-247-5_Horizontal_TabUp +Package_TO_SOT_THT:TO-247-5_Vertical +Package_TO_SOT_THT:TO-251-2-1EP_Horizontal_TabDown +Package_TO_SOT_THT:TO-251-2_Vertical +Package_TO_SOT_THT:TO-251-3-1EP_Horizontal_TabDown +Package_TO_SOT_THT:TO-251-3_Vertical +Package_TO_SOT_THT:TO-262-3-1EP_Horizontal_TabDown +Package_TO_SOT_THT:TO-262-3_Vertical +Package_TO_SOT_THT:TO-262-5-1EP_Horizontal_TabDown +Package_TO_SOT_THT:TO-262-5_Vertical +Package_TO_SOT_THT:TO-264-2_Horizontal_TabDown +Package_TO_SOT_THT:TO-264-2_Horizontal_TabUp +Package_TO_SOT_THT:TO-264-2_Vertical +Package_TO_SOT_THT:TO-264-3_Horizontal_TabDown +Package_TO_SOT_THT:TO-264-3_Horizontal_TabUp +Package_TO_SOT_THT:TO-264-3_Vertical +Package_TO_SOT_THT:TO-264-5_Horizontal_TabDown +Package_TO_SOT_THT:TO-264-5_Horizontal_TabUp +Package_TO_SOT_THT:TO-264-5_Vertical +Package_TO_SOT_THT:TO-3 +Package_TO_SOT_THT:TO-33-4 +Package_TO_SOT_THT:TO-33-4_Window +Package_TO_SOT_THT:TO-38-2 +Package_TO_SOT_THT:TO-38-2_Window +Package_TO_SOT_THT:TO-38-3 +Package_TO_SOT_THT:TO-38-3_Window +Package_TO_SOT_THT:TO-39-10 +Package_TO_SOT_THT:TO-39-10_Window +Package_TO_SOT_THT:TO-39-2 +Package_TO_SOT_THT:TO-39-2_Window +Package_TO_SOT_THT:TO-39-3 +Package_TO_SOT_THT:TO-39-3_Window +Package_TO_SOT_THT:TO-39-4 +Package_TO_SOT_THT:TO-39-4_Window +Package_TO_SOT_THT:TO-39-6 +Package_TO_SOT_THT:TO-39-6_Window +Package_TO_SOT_THT:TO-39-8 +Package_TO_SOT_THT:TO-39-8_Window +Package_TO_SOT_THT:TO-3P-3_Horizontal_TabDown +Package_TO_SOT_THT:TO-3P-3_Horizontal_TabUp +Package_TO_SOT_THT:TO-3P-3_Vertical +Package_TO_SOT_THT:TO-3PB-3_Horizontal_TabDown +Package_TO_SOT_THT:TO-3PB-3_Horizontal_TabUp +Package_TO_SOT_THT:TO-3PB-3_Vertical +Package_TO_SOT_THT:TO-46-2 +Package_TO_SOT_THT:TO-46-2_Pin2Center +Package_TO_SOT_THT:TO-46-2_Pin2Center_Window +Package_TO_SOT_THT:TO-46-2_Window +Package_TO_SOT_THT:TO-46-3 +Package_TO_SOT_THT:TO-46-3_Pin2Center +Package_TO_SOT_THT:TO-46-3_Pin2Center_Window +Package_TO_SOT_THT:TO-46-3_Window +Package_TO_SOT_THT:TO-46-4 +Package_TO_SOT_THT:TO-46-4_Window +Package_TO_SOT_THT:TO-5-10 +Package_TO_SOT_THT:TO-5-10_Window +Package_TO_SOT_THT:TO-5-2 +Package_TO_SOT_THT:TO-5-2_Window +Package_TO_SOT_THT:TO-5-3 +Package_TO_SOT_THT:TO-5-3_Window +Package_TO_SOT_THT:TO-5-4 +Package_TO_SOT_THT:TO-5-4_Window +Package_TO_SOT_THT:TO-5-6 +Package_TO_SOT_THT:TO-5-6_Window +Package_TO_SOT_THT:TO-5-8 +Package_TO_SOT_THT:TO-5-8_PD5.08 +Package_TO_SOT_THT:TO-5-8_PD5.08_Window +Package_TO_SOT_THT:TO-5-8_Window +Package_TO_SOT_THT:TO-52-2 +Package_TO_SOT_THT:TO-52-2_Window +Package_TO_SOT_THT:TO-52-3 +Package_TO_SOT_THT:TO-52-3_Window +Package_TO_SOT_THT:TO-72-4 +Package_TO_SOT_THT:TO-72-4_Window +Package_TO_SOT_THT:TO-75-6 +Package_TO_SOT_THT:TO-75-6_Window +Package_TO_SOT_THT:TO-78-10 +Package_TO_SOT_THT:TO-78-10_Window +Package_TO_SOT_THT:TO-78-6 +Package_TO_SOT_THT:TO-78-6_Window +Package_TO_SOT_THT:TO-78-8 +Package_TO_SOT_THT:TO-78-8_Window +Package_TO_SOT_THT:TO-8-2 +Package_TO_SOT_THT:TO-8-2_Window +Package_TO_SOT_THT:TO-8-3 +Package_TO_SOT_THT:TO-8-3_Window +Package_TO_SOT_THT:TO-92-2 +Package_TO_SOT_THT:TO-92-2_Horizontal1 +Package_TO_SOT_THT:TO-92-2_Horizontal2 +Package_TO_SOT_THT:TO-92-2_W4.0mm_Horizontal_FlatSideDown +Package_TO_SOT_THT:TO-92-2_W4.0mm_Horizontal_FlatSideUp +Package_TO_SOT_THT:TO-92-2_Wide +Package_TO_SOT_THT:TO-92 +Package_TO_SOT_THT:TO-92Flat +Package_TO_SOT_THT:TO-92L +Package_TO_SOT_THT:TO-92L_HandSolder +Package_TO_SOT_THT:TO-92L_Inline +Package_TO_SOT_THT:TO-92L_Inline_Wide +Package_TO_SOT_THT:TO-92L_Wide +Package_TO_SOT_THT:TO-92Mini-2 +Package_TO_SOT_THT:TO-92S-2 +Package_TO_SOT_THT:TO-92S +Package_TO_SOT_THT:TO-92S_Wide +Package_TO_SOT_THT:TO-92_HandSolder +Package_TO_SOT_THT:TO-92_Horizontal1 +Package_TO_SOT_THT:TO-92_Horizontal2 +Package_TO_SOT_THT:TO-92_Inline +Package_TO_SOT_THT:TO-92_Inline_Horizontal1 +Package_TO_SOT_THT:TO-92_Inline_Horizontal2 +Package_TO_SOT_THT:TO-92_Inline_W4.0mm_Horizontal_FlatSideDown +Package_TO_SOT_THT:TO-92_Inline_W4.0mm_Horizontal_FlatSideUp +Package_TO_SOT_THT:TO-92_Inline_Wide +Package_TO_SOT_THT:TO-92_W4.0mm_StaggerEven_Horizontal_FlatSideDown +Package_TO_SOT_THT:TO-92_W4.0mm_StaggerEven_Horizontal_FlatSideUp +Package_TO_SOT_THT:TO-92_Wide +Package_TO_SOT_THT:TO-99-6 +Package_TO_SOT_THT:TO-99-6_Window +Package_TO_SOT_THT:TO-99-8 +Package_TO_SOT_THT:TO-99-8_Window +Potentiometer_SMD:Potentiometer_ACP_CA14-VSMD_Vertical +Potentiometer_SMD:Potentiometer_ACP_CA14-VSMD_Vertical_Hole +Potentiometer_SMD:Potentiometer_ACP_CA6-VSMD_Vertical +Potentiometer_SMD:Potentiometer_ACP_CA6-VSMD_Vertical_Hole +Potentiometer_SMD:Potentiometer_ACP_CA9-VSMD_Vertical +Potentiometer_SMD:Potentiometer_ACP_CA9-VSMD_Vertical_Hole +Potentiometer_SMD:Potentiometer_Bourns_3214G_Horizontal +Potentiometer_SMD:Potentiometer_Bourns_3214J_Horizontal +Potentiometer_SMD:Potentiometer_Bourns_3214W_Vertical +Potentiometer_SMD:Potentiometer_Bourns_3214X_Vertical +Potentiometer_SMD:Potentiometer_Bourns_3224G_Horizontal +Potentiometer_SMD:Potentiometer_Bourns_3224J_Horizontal +Potentiometer_SMD:Potentiometer_Bourns_3224W_Vertical +Potentiometer_SMD:Potentiometer_Bourns_3224X_Vertical +Potentiometer_SMD:Potentiometer_Bourns_3269P_Horizontal +Potentiometer_SMD:Potentiometer_Bourns_3269W_Vertical +Potentiometer_SMD:Potentiometer_Bourns_3269X_Horizontal +Potentiometer_SMD:Potentiometer_Bourns_3314G_Vertical +Potentiometer_SMD:Potentiometer_Bourns_3314J_Vertical +Potentiometer_SMD:Potentiometer_Bourns_3314R-1_Vertical_Hole +Potentiometer_SMD:Potentiometer_Bourns_3314R-GM5_Vertical +Potentiometer_SMD:Potentiometer_Bourns_3314S_Horizontal +Potentiometer_SMD:Potentiometer_Bourns_PRS11S_Vertical +Potentiometer_SMD:Potentiometer_Bourns_TC33X_Vertical +Potentiometer_SMD:Potentiometer_Vishay_TS53YJ_Vertical +Potentiometer_SMD:Potentiometer_Vishay_TS53YL_Vertical +Potentiometer_THT:Potentiometer_ACP_CA14-H2,5_Horizontal +Potentiometer_THT:Potentiometer_ACP_CA14-H4_Horizontal +Potentiometer_THT:Potentiometer_ACP_CA14-H5_Horizontal +Potentiometer_THT:Potentiometer_ACP_CA14V-15_Vertical +Potentiometer_THT:Potentiometer_ACP_CA14V-15_Vertical_Hole +Potentiometer_THT:Potentiometer_ACP_CA6-H2,5_Horizontal +Potentiometer_THT:Potentiometer_ACP_CA9-H2,5_Horizontal +Potentiometer_THT:Potentiometer_ACP_CA9-H3,8_Horizontal +Potentiometer_THT:Potentiometer_ACP_CA9-H5_Horizontal +Potentiometer_THT:Potentiometer_ACP_CA9-V10_Vertical +Potentiometer_THT:Potentiometer_ACP_CA9-V10_Vertical_Hole +Potentiometer_THT:Potentiometer_Alpha_RD901F-40-00D_Single_Vertical +Potentiometer_THT:Potentiometer_Alpha_RD901F-40-00D_Single_Vertical_CircularHoles +Potentiometer_THT:Potentiometer_Alpha_RD902F-40-00D_Dual_Vertical +Potentiometer_THT:Potentiometer_Alpha_RD902F-40-00D_Dual_Vertical_CircularHoles +Potentiometer_THT:Potentiometer_Alps_RK097_Dual_Horizontal +Potentiometer_THT:Potentiometer_Alps_RK097_Dual_Horizontal_Switch +Potentiometer_THT:Potentiometer_Alps_RK097_Single_Horizontal +Potentiometer_THT:Potentiometer_Alps_RK097_Single_Horizontal_Switch +Potentiometer_THT:Potentiometer_Alps_RK09K_Single_Horizontal +Potentiometer_THT:Potentiometer_Alps_RK09K_Single_Vertical +Potentiometer_THT:Potentiometer_Alps_RK09L_Double_Horizontal +Potentiometer_THT:Potentiometer_Alps_RK09L_Double_Vertical +Potentiometer_THT:Potentiometer_Alps_RK09L_Single_Horizontal +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 +Potentiometer_THT:Potentiometer_Bourns_3006Y_Horizontal +Potentiometer_THT:Potentiometer_Bourns_3009P_Horizontal +Potentiometer_THT:Potentiometer_Bourns_3009Y_Horizontal +Potentiometer_THT:Potentiometer_Bourns_3266P_Horizontal +Potentiometer_THT:Potentiometer_Bourns_3266W_Vertical +Potentiometer_THT:Potentiometer_Bourns_3266X_Horizontal +Potentiometer_THT:Potentiometer_Bourns_3266Y_Vertical +Potentiometer_THT:Potentiometer_Bourns_3266Z_Horizontal +Potentiometer_THT:Potentiometer_Bourns_3296P_Horizontal +Potentiometer_THT:Potentiometer_Bourns_3296W_Vertical +Potentiometer_THT:Potentiometer_Bourns_3296X_Horizontal +Potentiometer_THT:Potentiometer_Bourns_3296Y_Vertical +Potentiometer_THT:Potentiometer_Bourns_3296Z_Horizontal +Potentiometer_THT:Potentiometer_Bourns_3299P_Horizontal +Potentiometer_THT:Potentiometer_Bourns_3299W_Vertical +Potentiometer_THT:Potentiometer_Bourns_3299X_Horizontal +Potentiometer_THT:Potentiometer_Bourns_3299Y_Vertical +Potentiometer_THT:Potentiometer_Bourns_3299Z_Horizontal +Potentiometer_THT:Potentiometer_Bourns_3339H_Vertical +Potentiometer_THT:Potentiometer_Bourns_3339P_Vertical +Potentiometer_THT:Potentiometer_Bourns_3339P_Vertical_HandSoldering +Potentiometer_THT:Potentiometer_Bourns_3339S_Horizontal +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_Horizontal +Potentiometer_THT:Potentiometer_Bourns_3386X_Horizontal +Potentiometer_THT:Potentiometer_Bourns_PTA1543_Single_Slide +Potentiometer_THT:Potentiometer_Bourns_PTA2043_Single_Slide +Potentiometer_THT:Potentiometer_Bourns_PTA3043_Single_Slide +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 +Potentiometer_THT:Potentiometer_Piher_PC-16_Single_Horizontal +Potentiometer_THT:Potentiometer_Piher_PC-16_Single_Vertical +Potentiometer_THT:Potentiometer_Piher_PC-16_Triple_Horizontal +Potentiometer_THT:Potentiometer_Piher_PT-10-H01_Horizontal +Potentiometer_THT:Potentiometer_Piher_PT-10-H05_Horizontal +Potentiometer_THT:Potentiometer_Piher_PT-10-V05_Vertical +Potentiometer_THT:Potentiometer_Piher_PT-10-V10_Vertical +Potentiometer_THT:Potentiometer_Piher_PT-10-V10_Vertical_Hole +Potentiometer_THT:Potentiometer_Piher_PT-15-H01_Horizontal +Potentiometer_THT:Potentiometer_Piher_PT-15-H05_Horizontal +Potentiometer_THT:Potentiometer_Piher_PT-15-H06_Horizontal +Potentiometer_THT:Potentiometer_Piher_PT-15-H25_Horizontal +Potentiometer_THT:Potentiometer_Piher_PT-15-V02_Vertical +Potentiometer_THT:Potentiometer_Piher_PT-15-V02_Vertical_Hole +Potentiometer_THT:Potentiometer_Piher_PT-15-V15_Vertical +Potentiometer_THT:Potentiometer_Piher_PT-15-V15_Vertical_Hole +Potentiometer_THT:Potentiometer_Piher_PT-6-H_Horizontal +Potentiometer_THT:Potentiometer_Piher_PT-6-V_Vertical +Potentiometer_THT:Potentiometer_Piher_PT-6-V_Vertical_Hole +Potentiometer_THT:Potentiometer_Piher_T-16H_Double_Horizontal +Potentiometer_THT:Potentiometer_Piher_T-16H_Single_Horizontal +Potentiometer_THT:Potentiometer_Piher_T-16L_Single_Vertical_Hole +Potentiometer_THT:Potentiometer_Runtron_RM-063_Horizontal +Potentiometer_THT:Potentiometer_Runtron_RM-065_Vertical +Potentiometer_THT:Potentiometer_TT_P0915N +Potentiometer_THT:Potentiometer_Vishay_148-149_Dual_Horizontal +Potentiometer_THT:Potentiometer_Vishay_148-149_Single_Horizontal +Potentiometer_THT:Potentiometer_Vishay_148-149_Single_Vertical +Potentiometer_THT:Potentiometer_Vishay_148E-149E_Dual_Horizontal +Potentiometer_THT:Potentiometer_Vishay_148E-149E_Single_Horizontal +Potentiometer_THT:Potentiometer_Vishay_248BH-249BH_Single_Horizontal +Potentiometer_THT:Potentiometer_Vishay_248GJ-249GJ_Single_Horizontal +Potentiometer_THT:Potentiometer_Vishay_248GJ-249GJ_Single_Vertical +Potentiometer_THT:Potentiometer_Vishay_43_Horizontal +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 +Relay_SMD:Relay_DPDT_Kemet_EE2_NU +Relay_SMD:Relay_DPDT_Kemet_EE2_NUH +Relay_SMD:Relay_DPDT_Kemet_EE2_NUH_DoubleCoil +Relay_SMD:Relay_DPDT_Kemet_EE2_NUX_DoubleCoil +Relay_SMD:Relay_DPDT_Kemet_EE2_NUX_NKX +Relay_SMD:Relay_DPDT_Kemet_EE2_NU_DoubleCoil +Relay_SMD:Relay_DPDT_Omron_G6H-2F +Relay_SMD:Relay_DPDT_Omron_G6K-2F-Y +Relay_SMD:Relay_DPDT_Omron_G6K-2F +Relay_SMD:Relay_DPDT_Omron_G6K-2G-Y +Relay_SMD:Relay_DPDT_Omron_G6K-2G +Relay_SMD:Relay_DPDT_Omron_G6S-2F +Relay_SMD:Relay_DPDT_Omron_G6S-2G +Relay_SMD:Relay_DPDT_Omron_G6SK-2F +Relay_SMD:Relay_DPDT_Omron_G6SK-2G +Relay_SMD:Relay_Fujitsu_FTR-B3S +Relay_SMD:Relay_SPDT_AXICOM_HF3Series_50ohms_Pitch1.27mm +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_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_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 +Relay_THT:Relay_DPDT_Omron_G6S-2 +Relay_THT:Relay_DPDT_Omron_G6SK-2 +Relay_THT:Relay_DPDT_Panasonic_JW2 +Relay_THT:Relay_DPDT_Schrack-RT2-FormC-Dual-Coil_RM5mm +Relay_THT:Relay_DPDT_Schrack-RT2-FormC_RM5mm +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 +Relay_THT:Relay_SPDT_Finder_36.11 +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 +Relay_THT:Relay_SPDT_Omron_G2RL-1-E +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 +Relay_THT:Relay_SPDT_RAYEX-L90S +Relay_THT:Relay_SPDT_SANYOU_SRD_Series_Form_C +Relay_THT:Relay_SPDT_Schrack-RP-II-1-16A-FormC_RM5mm +Relay_THT:Relay_SPDT_Schrack-RP-II-1-FormC_RM3.5mm +Relay_THT:Relay_SPDT_Schrack-RP-II-1-FormC_RM5mm +Relay_THT:Relay_SPDT_Schrack-RT1-16A-FormC_RM5mm +Relay_THT:Relay_SPDT_Schrack-RT1-FormC_RM3.5mm +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 +Relay_THT:Relay_SPST_PotterBrumfield_T9AP1D52_12V30A +Relay_THT:Relay_SPST_RAYEX-L90A +Relay_THT:Relay_SPST_RAYEX-L90AS +Relay_THT:Relay_SPST_RAYEX-L90B +Relay_THT:Relay_SPST_RAYEX-L90BS +Relay_THT:Relay_SPST_SANYOU_SRD_Series_Form_A +Relay_THT:Relay_SPST_SANYOU_SRD_Series_Form_B +Relay_THT:Relay_SPST_Schrack-RP-II-1-16A-FormA_RM5mm +Relay_THT:Relay_SPST_Schrack-RP-II-1-FormA_RM3.5mm +Relay_THT:Relay_SPST_Schrack-RP-II-1-FormA_RM5mm +Relay_THT:Relay_SPST_Schrack-RP3SL-1coil_RM5mm +Relay_THT:Relay_SPST_Schrack-RP3SL_RM5mm +Relay_THT:Relay_SPST_Schrack-RT1-16A-FormA_RM5mm +Relay_THT:Relay_SPST_Schrack-RT1-FormA_RM3.5mm +Relay_THT:Relay_SPST_Schrack-RT1-FormA_RM5mm +Relay_THT:Relay_SPST_StandexMeder_MS_Form1AB +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 +Resistor_SMD:R_0201_0603Metric_Pad0.64x0.40mm_HandSolder +Resistor_SMD:R_0402_1005Metric +Resistor_SMD:R_0402_1005Metric_Pad0.72x0.64mm_HandSolder +Resistor_SMD:R_0603_1608Metric +Resistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolder +Resistor_SMD:R_0612_1632Metric +Resistor_SMD:R_0612_1632Metric_Pad1.18x3.40mm_HandSolder +Resistor_SMD:R_0805_2012Metric +Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder +Resistor_SMD:R_0815_2038Metric +Resistor_SMD:R_0815_2038Metric_Pad1.20x4.05mm_HandSolder +Resistor_SMD:R_1020_2550Metric +Resistor_SMD:R_1020_2550Metric_Pad1.33x5.20mm_HandSolder +Resistor_SMD:R_1206_3216Metric +Resistor_SMD:R_1206_3216Metric_Pad1.30x1.75mm_HandSolder +Resistor_SMD:R_1210_3225Metric +Resistor_SMD:R_1210_3225Metric_Pad1.30x2.65mm_HandSolder +Resistor_SMD:R_1218_3246Metric +Resistor_SMD:R_1218_3246Metric_Pad1.22x4.75mm_HandSolder +Resistor_SMD:R_1812_4532Metric +Resistor_SMD:R_1812_4532Metric_Pad1.30x3.40mm_HandSolder +Resistor_SMD:R_2010_5025Metric +Resistor_SMD:R_2010_5025Metric_Pad1.40x2.65mm_HandSolder +Resistor_SMD:R_2512_6332Metric +Resistor_SMD:R_2512_6332Metric_Pad1.40x3.35mm_HandSolder +Resistor_SMD:R_2816_7142Metric +Resistor_SMD:R_2816_7142Metric_Pad3.20x4.45mm_HandSolder +Resistor_SMD:R_4020_10251Metric +Resistor_SMD:R_4020_10251Metric_Pad1.65x5.30mm_HandSolder +Resistor_SMD:R_Array_Concave_2x0603 +Resistor_SMD:R_Array_Concave_4x0402 +Resistor_SMD:R_Array_Concave_4x0603 +Resistor_SMD:R_Array_Convex_2x0402 +Resistor_SMD:R_Array_Convex_2x0603 +Resistor_SMD:R_Array_Convex_2x0606 +Resistor_SMD:R_Array_Convex_2x1206 +Resistor_SMD:R_Array_Convex_4x0402 +Resistor_SMD:R_Array_Convex_4x0603 +Resistor_SMD:R_Array_Convex_4x0612 +Resistor_SMD:R_Array_Convex_4x1206 +Resistor_SMD:R_Array_Convex_5x0603 +Resistor_SMD:R_Array_Convex_5x1206 +Resistor_SMD:R_Array_Convex_8x0602 +Resistor_SMD:R_Cat16-2 +Resistor_SMD:R_Cat16-4 +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 +Resistor_SMD:R_Shunt_Ohmite_LVK25 +Resistor_SMD:R_Shunt_Vishay_WSK2512_6332Metric_T1.19mm +Resistor_SMD:R_Shunt_Vishay_WSK2512_6332Metric_T2.21mm +Resistor_SMD:R_Shunt_Vishay_WSK2512_6332Metric_T2.66mm +Resistor_SMD:R_Shunt_Vishay_WSKW0612 +Resistor_SMD:R_Shunt_Vishay_WSR2_WSR3 +Resistor_SMD:R_Shunt_Vishay_WSR2_WSR3_KelvinConnection +Resistor_THT:R_Array_SIP10 +Resistor_THT:R_Array_SIP11 +Resistor_THT:R_Array_SIP12 +Resistor_THT:R_Array_SIP13 +Resistor_THT:R_Array_SIP14 +Resistor_THT:R_Array_SIP4 +Resistor_THT:R_Array_SIP5 +Resistor_THT:R_Array_SIP6 +Resistor_THT:R_Array_SIP7 +Resistor_THT:R_Array_SIP8 +Resistor_THT:R_Array_SIP9 +Resistor_THT:R_Axial_DIN0204_L3.6mm_D1.6mm_P1.90mm_Vertical +Resistor_THT:R_Axial_DIN0204_L3.6mm_D1.6mm_P2.54mm_Vertical +Resistor_THT:R_Axial_DIN0204_L3.6mm_D1.6mm_P5.08mm_Horizontal +Resistor_THT:R_Axial_DIN0204_L3.6mm_D1.6mm_P5.08mm_Vertical +Resistor_THT:R_Axial_DIN0204_L3.6mm_D1.6mm_P7.62mm_Horizontal +Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal +Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P15.24mm_Horizontal +Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical +Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P5.08mm_Vertical +Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal +Resistor_THT:R_Axial_DIN0309_L9.0mm_D3.2mm_P12.70mm_Horizontal +Resistor_THT:R_Axial_DIN0309_L9.0mm_D3.2mm_P15.24mm_Horizontal +Resistor_THT:R_Axial_DIN0309_L9.0mm_D3.2mm_P2.54mm_Vertical +Resistor_THT:R_Axial_DIN0309_L9.0mm_D3.2mm_P20.32mm_Horizontal +Resistor_THT:R_Axial_DIN0309_L9.0mm_D3.2mm_P25.40mm_Horizontal +Resistor_THT:R_Axial_DIN0309_L9.0mm_D3.2mm_P5.08mm_Vertical +Resistor_THT:R_Axial_DIN0411_L9.9mm_D3.6mm_P12.70mm_Horizontal +Resistor_THT:R_Axial_DIN0411_L9.9mm_D3.6mm_P15.24mm_Horizontal +Resistor_THT:R_Axial_DIN0411_L9.9mm_D3.6mm_P20.32mm_Horizontal +Resistor_THT:R_Axial_DIN0411_L9.9mm_D3.6mm_P25.40mm_Horizontal +Resistor_THT:R_Axial_DIN0411_L9.9mm_D3.6mm_P5.08mm_Vertical +Resistor_THT:R_Axial_DIN0411_L9.9mm_D3.6mm_P7.62mm_Vertical +Resistor_THT:R_Axial_DIN0414_L11.9mm_D4.5mm_P15.24mm_Horizontal +Resistor_THT:R_Axial_DIN0414_L11.9mm_D4.5mm_P20.32mm_Horizontal +Resistor_THT:R_Axial_DIN0414_L11.9mm_D4.5mm_P25.40mm_Horizontal +Resistor_THT:R_Axial_DIN0414_L11.9mm_D4.5mm_P5.08mm_Vertical +Resistor_THT:R_Axial_DIN0414_L11.9mm_D4.5mm_P7.62mm_Vertical +Resistor_THT:R_Axial_DIN0516_L15.5mm_D5.0mm_P20.32mm_Horizontal +Resistor_THT:R_Axial_DIN0516_L15.5mm_D5.0mm_P25.40mm_Horizontal +Resistor_THT:R_Axial_DIN0516_L15.5mm_D5.0mm_P30.48mm_Horizontal +Resistor_THT:R_Axial_DIN0516_L15.5mm_D5.0mm_P5.08mm_Vertical +Resistor_THT:R_Axial_DIN0516_L15.5mm_D5.0mm_P7.62mm_Vertical +Resistor_THT:R_Axial_DIN0614_L14.3mm_D5.7mm_P15.24mm_Horizontal +Resistor_THT:R_Axial_DIN0614_L14.3mm_D5.7mm_P20.32mm_Horizontal +Resistor_THT:R_Axial_DIN0614_L14.3mm_D5.7mm_P25.40mm_Horizontal +Resistor_THT:R_Axial_DIN0614_L14.3mm_D5.7mm_P5.08mm_Vertical +Resistor_THT:R_Axial_DIN0614_L14.3mm_D5.7mm_P7.62mm_Vertical +Resistor_THT:R_Axial_DIN0617_L17.0mm_D6.0mm_P20.32mm_Horizontal +Resistor_THT:R_Axial_DIN0617_L17.0mm_D6.0mm_P25.40mm_Horizontal +Resistor_THT:R_Axial_DIN0617_L17.0mm_D6.0mm_P30.48mm_Horizontal +Resistor_THT:R_Axial_DIN0617_L17.0mm_D6.0mm_P5.08mm_Vertical +Resistor_THT:R_Axial_DIN0617_L17.0mm_D6.0mm_P7.62mm_Vertical +Resistor_THT:R_Axial_DIN0918_L18.0mm_D9.0mm_P22.86mm_Horizontal +Resistor_THT:R_Axial_DIN0918_L18.0mm_D9.0mm_P25.40mm_Horizontal +Resistor_THT:R_Axial_DIN0918_L18.0mm_D9.0mm_P30.48mm_Horizontal +Resistor_THT:R_Axial_DIN0918_L18.0mm_D9.0mm_P7.62mm_Vertical +Resistor_THT:R_Axial_DIN0922_L20.0mm_D9.0mm_P25.40mm_Horizontal +Resistor_THT:R_Axial_DIN0922_L20.0mm_D9.0mm_P30.48mm_Horizontal +Resistor_THT:R_Axial_DIN0922_L20.0mm_D9.0mm_P7.62mm_Vertical +Resistor_THT:R_Axial_Power_L20.0mm_W6.4mm_P22.40mm +Resistor_THT:R_Axial_Power_L20.0mm_W6.4mm_P25.40mm +Resistor_THT:R_Axial_Power_L20.0mm_W6.4mm_P30.48mm +Resistor_THT:R_Axial_Power_L20.0mm_W6.4mm_P5.08mm_Vertical +Resistor_THT:R_Axial_Power_L20.0mm_W6.4mm_P7.62mm_Vertical +Resistor_THT:R_Axial_Power_L25.0mm_W6.4mm_P27.94mm +Resistor_THT:R_Axial_Power_L25.0mm_W6.4mm_P30.48mm +Resistor_THT:R_Axial_Power_L25.0mm_W9.0mm_P10.16mm_Vertical +Resistor_THT:R_Axial_Power_L25.0mm_W9.0mm_P27.94mm +Resistor_THT:R_Axial_Power_L25.0mm_W9.0mm_P30.48mm +Resistor_THT:R_Axial_Power_L25.0mm_W9.0mm_P7.62mm_Vertical +Resistor_THT:R_Axial_Power_L38.0mm_W6.4mm_P40.64mm +Resistor_THT:R_Axial_Power_L38.0mm_W6.4mm_P45.72mm +Resistor_THT:R_Axial_Power_L38.0mm_W9.0mm_P40.64mm +Resistor_THT:R_Axial_Power_L38.0mm_W9.0mm_P45.72mm +Resistor_THT:R_Axial_Power_L48.0mm_W12.5mm_P10.16mm_Vertical +Resistor_THT:R_Axial_Power_L48.0mm_W12.5mm_P55.88mm +Resistor_THT:R_Axial_Power_L48.0mm_W12.5mm_P60.96mm +Resistor_THT:R_Axial_Power_L48.0mm_W12.5mm_P7.62mm_Vertical +Resistor_THT:R_Axial_Power_L50.0mm_W9.0mm_P55.88mm +Resistor_THT:R_Axial_Power_L50.0mm_W9.0mm_P60.96mm +Resistor_THT:R_Axial_Power_L60.0mm_W14.0mm_P10.16mm_Vertical +Resistor_THT:R_Axial_Power_L60.0mm_W14.0mm_P66.04mm +Resistor_THT:R_Axial_Power_L60.0mm_W14.0mm_P71.12mm +Resistor_THT:R_Axial_Power_L75.0mm_W9.0mm_P81.28mm +Resistor_THT:R_Axial_Power_L75.0mm_W9.0mm_P86.36mm +Resistor_THT:R_Axial_Shunt_L22.2mm_W8.0mm_PS14.30mm_P25.40mm +Resistor_THT:R_Axial_Shunt_L22.2mm_W9.5mm_PS14.30mm_P25.40mm +Resistor_THT:R_Axial_Shunt_L35.3mm_W9.5mm_PS25.40mm_P38.10mm +Resistor_THT:R_Axial_Shunt_L47.6mm_W12.7mm_PS34.93mm_P50.80mm +Resistor_THT:R_Axial_Shunt_L47.6mm_W9.5mm_PS34.93mm_P50.80mm +Resistor_THT:R_Bare_Metal_Element_L12.4mm_W4.8mm_P11.40mm +Resistor_THT:R_Bare_Metal_Element_L16.3mm_W4.8mm_P15.30mm +Resistor_THT:R_Bare_Metal_Element_L21.3mm_W4.8mm_P20.30mm +Resistor_THT:R_Box_L13.0mm_W4.0mm_P9.00mm +Resistor_THT:R_Box_L14.0mm_W5.0mm_P9.00mm +Resistor_THT:R_Box_L26.0mm_W5.0mm_P20.00mm +Resistor_THT:R_Box_L8.4mm_W2.5mm_P5.08mm +Resistor_THT:R_Radial_Power_L11.0mm_W7.0mm_P5.00mm +Resistor_THT:R_Radial_Power_L12.0mm_W8.0mm_P5.00mm +Resistor_THT:R_Radial_Power_L13.0mm_W9.0mm_P5.00mm +Resistor_THT:R_Radial_Power_L16.1mm_W9.0mm_P7.37mm +Resistor_THT:R_Radial_Power_L7.0mm_W8.0mm_Px2.40mm_Py2.30mm +Resistor_THT:R_Radial_Power_L9.0mm_W10.0mm_Px2.70mm_Py2.30mm +RF:Skyworks_SKY13575_639LF +RF:Skyworks_SKY65404-31 +RF_Antenna:Abracon_APAES868R8060C16-T +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 +RF_Antenna:Texas_SWRA117D_2.4GHz_Right +RF_Antenna:Texas_SWRA416_868MHz_915MHz +RF_Converter:Anaren_0805_2012Metric-6 +RF_Converter:Balun_Johanson_0896BM15A0001 +RF_Converter:Balun_Johanson_0900FM15K0039 +RF_Converter:Balun_Johanson_0900PC15J0013 +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 +RF_GPS:ublox_LEA +RF_GPS:ublox_MAX +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_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 +RF_Mini-Circuits:Mini-Circuits_CD542_H2.84mm +RF_Mini-Circuits:Mini-Circuits_CD542_LandPatternPL-052 +RF_Mini-Circuits:Mini-Circuits_CD542_LandPatternPL-094 +RF_Mini-Circuits:Mini-Circuits_CD636_H4.11mm +RF_Mini-Circuits:Mini-Circuits_CD636_LandPatternPL-035 +RF_Mini-Circuits:Mini-Circuits_CD637_H5.23mm +RF_Mini-Circuits:Mini-Circuits_CK605 +RF_Mini-Circuits:Mini-Circuits_CK605_LandPatternPL-012 +RF_Mini-Circuits:Mini-Circuits_DB1627 +RF_Mini-Circuits:Mini-Circuits_GP1212 +RF_Mini-Circuits:Mini-Circuits_GP1212_LandPatternPL-176 +RF_Mini-Circuits:Mini-Circuits_GP731 +RF_Mini-Circuits:Mini-Circuits_GP731_LandPatternPL-176 +RF_Mini-Circuits:Mini-Circuits_HF1139 +RF_Mini-Circuits:Mini-Circuits_HF1139_LandPatternPL-230 +RF_Mini-Circuits:Mini-Circuits_HQ1157 +RF_Mini-Circuits:Mini-Circuits_HZ1198 +RF_Mini-Circuits:Mini-Circuits_HZ1198_LandPatternPL-247 +RF_Mini-Circuits:Mini-Circuits_MMM168 +RF_Mini-Circuits:Mini-Circuits_MMM168_LandPatternPL-225 +RF_Mini-Circuits:Mini-Circuits_QQQ130_ClockwisePinNumbering +RF_Mini-Circuits:Mini-Circuits_QQQ130_LandPattern_PL-236_ClockwisePinNumbering +RF_Mini-Circuits:Mini-Circuits_TT1224_ClockwisePinNumbering +RF_Mini-Circuits:Mini-Circuits_TT1224_LandPatternPL-258_ClockwisePinNumbering +RF_Mini-Circuits:Mini-Circuits_TTT167 +RF_Mini-Circuits:Mini-Circuits_TTT167_LandPatternPL-079 +RF_Mini-Circuits:Mini-Circuits_YY161 +RF_Mini-Circuits:Mini-Circuits_YY161_LandPatternPL-049 +RF_Module:Ai-Thinker-Ra-01-LoRa +RF_Module:Astrocast_AST50147-00 +RF_Module:Atmel_ATSAMR21G18-MR210UA_NoRFPads +RF_Module:BLE112-A +RF_Module:BM78SPPS5xC2 +RF_Module:CMWX1ZZABZ +RF_Module:CYBLE-21Pin-10x10mm +RF_Module:DecaWave_DWM1001 +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 +RF_Module:Heltec_HT-CT62 +RF_Module:HOPERF_RFM69HW +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 +RF_Module:Modtronix_inAir9 +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 +RF_Shielding:Laird_Technologies_97-2003_12.70x13.37mm +RF_Shielding:Laird_Technologies_BMI-S-101_13.66x12.70mm +RF_Shielding:Laird_Technologies_BMI-S-102_16.50x16.50mm +RF_Shielding:Laird_Technologies_BMI-S-103_26.21x26.21mm +RF_Shielding:Laird_Technologies_BMI-S-104_32.00x32.00mm +RF_Shielding:Laird_Technologies_BMI-S-105_38.10x25.40mm +RF_Shielding:Laird_Technologies_BMI-S-106_36.83x33.68mm +RF_Shielding:Laird_Technologies_BMI-S-107_44.37x44.37mm +RF_Shielding:Laird_Technologies_BMI-S-201-F_13.66x12.70mm +RF_Shielding:Laird_Technologies_BMI-S-202-F_16.50x16.50mm +RF_Shielding:Laird_Technologies_BMI-S-203-F_26.21x26.21mm +RF_Shielding:Laird_Technologies_BMI-S-204-F_32.00x32.00mm +RF_Shielding:Laird_Technologies_BMI-S-205-F_38.10x25.40mm +RF_Shielding:Laird_Technologies_BMI-S-206-F_36.83x33.68mm +RF_Shielding:Laird_Technologies_BMI-S-207-F_44.37x44.37mm +RF_Shielding:Laird_Technologies_BMI-S-208-F_39.60x39.60mm +RF_Shielding:Laird_Technologies_BMI-S-209-F_29.36x18.50mm +RF_Shielding:Laird_Technologies_BMI-S-210-F_44.00x30.50mm +RF_Shielding:Laird_Technologies_BMI-S-230-F_50.8x38.1mm +RF_Shielding:Wuerth_36103205_20x20mm +RF_Shielding:Wuerth_36103255_25x25mm +RF_Shielding:Wuerth_36103305_30x30mm +RF_Shielding:Wuerth_36103505_50x50mm +RF_Shielding:Wuerth_36103605_60x60mm +RF_Shielding:Wuerth_36503205_20x20mm +RF_Shielding:Wuerth_36503255_25x25mm +RF_Shielding:Wuerth_36503305_30x30mm +RF_Shielding:Wuerth_36503505_50x50mm +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 +Rotary_Encoder:RotaryEncoder_Alps_EC12E-Switch_Vertical_H20mm_CircularMountingHoles +Rotary_Encoder:RotaryEncoder_Alps_EC12E_Vertical_H20mm +Rotary_Encoder:RotaryEncoder_Alps_EC12E_Vertical_H20mm_CircularMountingHoles +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 +Sensor:Avago_APDS-9960 +Sensor:LuminOX_LOX-O2 +Sensor:MQ-6 +Sensor:Rohm_RPR-0521RS +Sensor:Senseair_S8_Down +Sensor:Senseair_S8_Up +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 +Sensor_Audio:Infineon_PG-LLGA-5-2 +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 +Sensor_Current:AKM_CQ_VSOP-24_5.6x7.9mm_P0.65mm +Sensor_Current:AKM_CZ_SSOP-10_6.5x8.1mm_P0.95mm +Sensor_Current:Allegro_CB_PFF +Sensor_Current:Allegro_CB_PSF +Sensor_Current:Allegro_CB_PSS +Sensor_Current:Allegro_PSOF-7_4.8x6.4mm_P1.60mm +Sensor_Current:Allegro_QFN-12-10-1EP_3x3mm_P0.5mm +Sensor_Current:Allegro_QSOP-24_3.9x8.7mm_P0.635mm +Sensor_Current:Allegro_SIP-3 +Sensor_Current:Allegro_SIP-4 +Sensor_Current:Diodes_SIP-3_4.1x1.5mm_P1.27mm +Sensor_Current:Diodes_SIP-3_4.1x1.5mm_P2.65mm +Sensor_Current:Honeywell_CSLW +Sensor_Current:LEM_CKSR +Sensor_Current:LEM_HO40-NP +Sensor_Current:LEM_HO8-NP +Sensor_Current:LEM_HO8-NSM +Sensor_Current:LEM_HTFS +Sensor_Current:LEM_HX02-P +Sensor_Current:LEM_HX03-P-SP2 +Sensor_Current:LEM_HX04-P +Sensor_Current:LEM_HX05-NP +Sensor_Current:LEM_HX05-P-SP2 +Sensor_Current:LEM_HX06-P +Sensor_Current:LEM_HX10-NP +Sensor_Current:LEM_HX10-P-SP2 +Sensor_Current:LEM_HX15-NP +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 +Sensor_Humidity:Sensirion_DFN-8-1EP_2.5x2.5mm_P0.5mm_EP1.1x1.7mm +Sensor_Motion:Analog_LGA-16_3.25x3mm_P0.5mm_LayoutBorder3x5y +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 +Sensor_Pressure:Freescale_98ARH99089A +Sensor_Pressure:Honeywell_40PCxxxG1A +Sensor_Pressure:TE_MS5525DSO-DBxxxyS +Sensor_Pressure:TE_MS5837-xxBA +Sensor_Voltage:LEM_LV25-P +Socket:3M_Textool_240-1288-00-0602J_2x20_P2.54mm +Socket:DIP_Socket-14_W4.3_W5.08_W7.62_W10.16_W10.9_3M_214-3339-00-0602J +Socket:DIP_Socket-16_W4.3_W5.08_W7.62_W10.16_W10.9_3M_216-3340-00-0602J +Socket:DIP_Socket-18_W4.3_W5.08_W7.62_W10.16_W10.9_3M_218-3341-00-0602J +Socket:DIP_Socket-20_W4.3_W5.08_W7.62_W10.16_W10.9_3M_220-3342-00-0602J +Socket:DIP_Socket-22_W6.9_W7.62_W10.16_W12.7_W13.5_3M_222-3343-00-0602J +Socket:DIP_Socket-24_W11.9_W12.7_W15.24_W17.78_W18.5_3M_224-1275-00-0602J +Socket:DIP_Socket-24_W4.3_W5.08_W7.62_W10.16_W10.9_3M_224-5248-00-0602J +Socket:DIP_Socket-28_W11.9_W12.7_W15.24_W17.78_W18.5_3M_228-1277-00-0602J +Socket:DIP_Socket-28_W6.9_W7.62_W10.16_W12.7_W13.5_3M_228-4817-00-0602J +Socket:DIP_Socket-32_W11.9_W12.7_W15.24_W17.78_W18.5_3M_232-1285-00-0602J +Socket:DIP_Socket-40_W11.9_W12.7_W15.24_W17.78_W18.5_3M_240-1280-00-0602J +Socket:DIP_Socket-40_W22.1_W22.86_W25.4_W27.94_W28.7_3M_240-3639-00-0602J +Socket:DIP_Socket-42_W11.9_W12.7_W15.24_W17.78_W18.5_3M_242-1281-00-0602J +Socket:Wells_648-0482211SA01 +Symbol:CE-Logo_11.2x8mm_SilkScreen +Symbol:CE-Logo_16.8x12mm_SilkScreen +Symbol:CE-Logo_28x20mm_SilkScreen +Symbol:CE-Logo_42x30mm_SilkScreen +Symbol:CE-Logo_56.1x40mm_SilkScreen +Symbol:CE-Logo_8.5x6mm_SilkScreen +Symbol:EasterEgg_EWG1308-2013_ClassA +Symbol:ESD-Logo_13.2x12mm_SilkScreen +Symbol:ESD-Logo_22x20mm_SilkScreen +Symbol:ESD-Logo_33x30mm_SilkScreen +Symbol:ESD-Logo_44.1x40mm_SilkScreen +Symbol:ESD-Logo_6.6x6mm_SilkScreen +Symbol:ESD-Logo_8.9x8mm_SilkScreen +Symbol:FCC-Logo_14.6x12mm_SilkScreen +Symbol:FCC-Logo_24.2x20mm_SilkScreen +Symbol:FCC-Logo_36.3x30mm_SilkScreen +Symbol:FCC-Logo_48.3x40mm_SilkScreen +Symbol:FCC-Logo_7.3x6mm_SilkScreen +Symbol:FCC-Logo_9.6x8mm_SilkScreen +Symbol:KiCad-Logo2_12mm_Copper +Symbol:KiCad-Logo2_12mm_SilkScreen +Symbol:KiCad-Logo2_20mm_Copper +Symbol:KiCad-Logo2_20mm_SilkScreen +Symbol:KiCad-Logo2_30mm_Copper +Symbol:KiCad-Logo2_30mm_SilkScreen +Symbol:KiCad-Logo2_40mm_Copper +Symbol:KiCad-Logo2_40mm_SilkScreen +Symbol:KiCad-Logo2_5mm_Copper +Symbol:KiCad-Logo2_5mm_SilkScreen +Symbol:KiCad-Logo2_6mm_Copper +Symbol:KiCad-Logo2_6mm_SilkScreen +Symbol:KiCad-Logo2_8mm_Copper +Symbol:KiCad-Logo2_8mm_SilkScreen +Symbol:KiCad-Logo_12mm_Copper +Symbol:KiCad-Logo_12mm_SilkScreen +Symbol:KiCad-Logo_20mm_Copper +Symbol:KiCad-Logo_20mm_SilkScreen +Symbol:KiCad-Logo_30mm_Copper +Symbol:KiCad-Logo_30mm_SilkScreen +Symbol:KiCad-Logo_40mm_Copper +Symbol:KiCad-Logo_40mm_SilkScreen +Symbol:KiCad-Logo_5mm_Copper +Symbol:KiCad-Logo_5mm_SilkScreen +Symbol:KiCad-Logo_6mm_Copper +Symbol:KiCad-Logo_6mm_SilkScreen +Symbol:KiCad-Logo_8mm_Copper +Symbol:KiCad-Logo_8mm_SilkScreen +Symbol:OSHW-Logo2_14.6x12mm_Copper +Symbol:OSHW-Logo2_14.6x12mm_SilkScreen +Symbol:OSHW-Logo2_24.3x20mm_Copper +Symbol:OSHW-Logo2_24.3x20mm_SilkScreen +Symbol:OSHW-Logo2_36.5x30mm_Copper +Symbol:OSHW-Logo2_36.5x30mm_SilkScreen +Symbol:OSHW-Logo2_48.7x40mm_Copper +Symbol:OSHW-Logo2_48.7x40mm_SilkScreen +Symbol:OSHW-Logo2_7.3x6mm_Copper +Symbol:OSHW-Logo2_7.3x6mm_SilkScreen +Symbol:OSHW-Logo2_9.8x8mm_Copper +Symbol:OSHW-Logo2_9.8x8mm_SilkScreen +Symbol:OSHW-Logo_11.4x12mm_Copper +Symbol:OSHW-Logo_11.4x12mm_SilkScreen +Symbol:OSHW-Logo_19x20mm_Copper +Symbol:OSHW-Logo_19x20mm_SilkScreen +Symbol:OSHW-Logo_28.5x30mm_Copper +Symbol:OSHW-Logo_28.5x30mm_SilkScreen +Symbol:OSHW-Logo_38.1x40mm_Copper +Symbol:OSHW-Logo_38.1x40mm_SilkScreen +Symbol:OSHW-Logo_5.7x6mm_Copper +Symbol:OSHW-Logo_5.7x6mm_SilkScreen +Symbol:OSHW-Logo_7.5x8mm_Copper +Symbol:OSHW-Logo_7.5x8mm_SilkScreen +Symbol:OSHW-Symbol_13.4x12mm_Copper +Symbol:OSHW-Symbol_13.4x12mm_SilkScreen +Symbol:OSHW-Symbol_22.3x20mm_Copper +Symbol:OSHW-Symbol_22.3x20mm_SilkScreen +Symbol:OSHW-Symbol_33.5x30mm_Copper +Symbol:OSHW-Symbol_33.5x30mm_SilkScreen +Symbol:OSHW-Symbol_44.5x40mm_Copper +Symbol:OSHW-Symbol_44.5x40mm_SilkScreen +Symbol:OSHW-Symbol_6.7x6mm_Copper +Symbol:OSHW-Symbol_6.7x6mm_SilkScreen +Symbol:OSHW-Symbol_8.9x8mm_Copper +Symbol:OSHW-Symbol_8.9x8mm_SilkScreen +Symbol:Polarity_Center_Negative_12mm_SilkScreen +Symbol:Polarity_Center_Negative_20mm_SilkScreen +Symbol:Polarity_Center_Negative_30mm_SilkScreen +Symbol:Polarity_Center_Negative_40mm_SilkScreen +Symbol:Polarity_Center_Negative_6mm_SilkScreen +Symbol:Polarity_Center_Negative_8mm_SilkScreen +Symbol:Polarity_Center_Positive_12mm_SilkScreen +Symbol:Polarity_Center_Positive_20mm_SilkScreen +Symbol:Polarity_Center_Positive_30mm_SilkScreen +Symbol:Polarity_Center_Positive_40mm_SilkScreen +Symbol:Polarity_Center_Positive_6mm_SilkScreen +Symbol:Polarity_Center_Positive_8mm_SilkScreen +Symbol:RoHS-Logo_12mm_SilkScreen +Symbol:RoHS-Logo_20mm_SilkScreen +Symbol:RoHS-Logo_30mm_SilkScreen +Symbol:RoHS-Logo_40mm_SilkScreen +Symbol:RoHS-Logo_6mm_SilkScreen +Symbol:RoHS-Logo_8mm_SilkScreen +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 +Symbol:Symbol_CC-Noncommercial_CopperTop_Big +Symbol:Symbol_CC-Noncommercial_CopperTop_Small +Symbol:Symbol_CC-PublicDomain_CopperTop_Big +Symbol:Symbol_CC-PublicDomain_CopperTop_Small +Symbol:Symbol_CC-PublicDomain_SilkScreenTop_Big +Symbol:Symbol_CC-ShareAlike_CopperTop_Big +Symbol:Symbol_CC-ShareAlike_CopperTop_Small +Symbol:Symbol_CreativeCommonsPublicDomain_CopperTop_Small +Symbol:Symbol_CreativeCommonsPublicDomain_SilkScreenTop_Small +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_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_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 +Symbol:UKCA-Logo_40x40mm_SilkScreen +Symbol:UKCA-Logo_6x6mm_SilkScreen +Symbol:UKCA-Logo_8x8mm_SilkScreen +Symbol:WEEE-Logo_14x20mm_SilkScreen +Symbol:WEEE-Logo_21x30mm_SilkScreen +Symbol:WEEE-Logo_28.1x40mm_SilkScreen +Symbol:WEEE-Logo_4.2x6mm_SilkScreen +Symbol:WEEE-Logo_5.6x8mm_SilkScreen +Symbol:WEEE-Logo_8.4x12mm_SilkScreen +TerminalBlock:TerminalBlock_Altech_AK300-2_P5.00mm +TerminalBlock:TerminalBlock_Altech_AK300-3_P5.00mm +TerminalBlock:TerminalBlock_Altech_AK300-4_P5.00mm +TerminalBlock:TerminalBlock_bornier-2_P5.08mm +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 +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x03_P3.50mm_Vertical +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x04_P3.50mm_Horizontal +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x04_P3.50mm_Vertical +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x05_P3.50mm_Horizontal +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x05_P3.50mm_Vertical +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x06_P3.50mm_Horizontal +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x06_P3.50mm_Vertical +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x07_P3.50mm_Horizontal +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x07_P3.50mm_Vertical +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x08_P3.50mm_Horizontal +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x08_P3.50mm_Vertical +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x09_P3.50mm_Horizontal +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x09_P3.50mm_Vertical +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x10_P3.50mm_Horizontal +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x10_P3.50mm_Vertical +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x11_P3.50mm_Horizontal +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x11_P3.50mm_Vertical +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x12_P3.50mm_Horizontal +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x12_P3.50mm_Vertical +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x13_P3.50mm_Horizontal +TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x13_P3.50mm_Vertical +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 +TerminalBlock_Altech:Altech_AK300_1x05_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x06_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x07_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x08_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x09_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x10_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x11_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x12_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x13_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x14_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x15_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x16_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x17_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x18_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x19_P5.00mm_45-Degree +TerminalBlock_Altech:Altech_AK300_1x20_P5.00mm_45-Degree +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 +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-05_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-06_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-07_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-08_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-09_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-10_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-11_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-12_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-13_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-14_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-15_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-16_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-17_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-18_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-19_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-20_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-21_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-22_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-23_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-24_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-25_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-26_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-27_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-28_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-29_P10.00mm +TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-30_P10.00mm +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_360271_1x01_Horizontal_ScrewM3.0_Boxed +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_360272_1x01_Horizontal_ScrewM2.6 +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_360273_1x01_Horizontal_ScrewM2.6_WireProtection +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_360291_1x01_Horizontal_ScrewM3.0_Boxed +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_360322_1x01_Horizontal_ScrewM3.0_WireProtection +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_360381_1x01_Horizontal_ScrewM3.0 +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_360410_1x01_Horizontal_ScrewM3.0 +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_360425_1x01_Horizontal_ScrewM4.0_Boxed +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type011_RT05502HBWC_1x02_P5.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type011_RT05503HBWC_1x03_P5.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type011_RT05504HBWC_1x04_P5.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type011_RT05505HBWC_1x05_P5.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type011_RT05506HBWC_1x06_P5.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type055_RT01502HDWU_1x02_P5.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type055_RT01503HDWU_1x03_P5.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type055_RT01504HDWU_1x04_P5.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type059_RT06302HBWC_1x02_P3.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type059_RT06303HBWC_1x03_P3.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type059_RT06304HBWC_1x04_P3.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type059_RT06305HBWC_1x05_P3.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type059_RT06306HBWC_1x06_P3.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type067_RT01902HDWC_1x02_P10.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type067_RT01903HDWC_1x03_P10.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type067_RT01904HDWC_1x04_P10.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type067_RT01905HDWC_1x05_P10.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type073_RT02602HBLU_1x02_P5.08mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type073_RT02603HBLU_1x03_P5.08mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type086_RT03402HBLC_1x02_P3.81mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type086_RT03403HBLC_1x03_P3.81mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type086_RT03404HBLC_1x04_P3.81mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type086_RT03405HBLC_1x05_P3.81mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type086_RT03406HBLC_1x06_P3.81mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type094_RT03502HBLU_1x02_P5.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type094_RT03503HBLU_1x03_P5.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type094_RT03504HBLU_1x04_P5.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type094_RT03505HBLU_1x05_P5.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type094_RT03506HBLU_1x06_P5.00mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type101_RT01602HBWC_1x02_P5.08mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type101_RT01603HBWC_1x03_P5.08mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type101_RT01604HBWC_1x04_P5.08mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type101_RT01605HBWC_1x05_P5.08mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type101_RT01606HBWC_1x06_P5.08mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type171_RT13702HBWC_1x02_P7.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type171_RT13703HBWC_1x03_P7.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type171_RT13704HBWC_1x04_P7.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type171_RT13705HBWC_1x05_P7.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type171_RT13706HBWC_1x06_P7.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type175_RT02702HBLC_1x02_P7.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type175_RT02703HBLC_1x03_P7.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type175_RT02704HBLC_1x04_P7.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type175_RT02705HBLC_1x05_P7.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type175_RT02706HBLC_1x06_P7.50mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type205_RT04502UBLC_1x02_P5.00mm_45Degree +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type205_RT04503UBLC_1x03_P5.00mm_45Degree +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type205_RT04504UBLC_1x04_P5.00mm_45Degree +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type205_RT04505UBLC_1x05_P5.00mm_45Degree +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type205_RT04506UBLC_1x06_P5.00mm_45Degree +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type701_RT11L02HGLU_1x02_P6.35mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type701_RT11L03HGLU_1x03_P6.35mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type703_RT10N02HGLU_1x02_P9.52mm_Horizontal +TerminalBlock_MetzConnect:TerminalBlock_MetzConnect_Type703_RT10N03HGLU_1x03_P9.52mm_Horizontal +TerminalBlock_Philmore:TerminalBlock_Philmore_TB132_1x02_P5.00mm_Horizontal +TerminalBlock_Philmore:TerminalBlock_Philmore_TB133_1x03_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-10-5.08_1x10_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-10_1x10_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-11-5.08_1x11_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-11_1x11_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-12-5.08_1x12_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-12_1x12_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-13-5.08_1x13_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-13_1x13_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-14-5.08_1x14_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-14_1x14_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-15-5.08_1x15_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-15_1x15_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-16-5.08_1x16_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-16_1x16_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-2-5.08_1x02_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-2_1x02_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-3-5.08_1x03_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-3_1x03_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-4-5.08_1x04_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-4_1x04_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-5-5.08_1x05_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-5_1x05_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-6-5.08_1x06_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-6_1x06_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-7-5.08_1x07_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-7_1x07_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-8-5.08_1x08_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-8_1x08_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-9-5.08_1x09_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-9_1x09_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-10-5.08_1x10_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-11-5.08_1x11_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-12-5.08_1x12_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-13-5.08_1x13_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-14-5.08_1x14_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-15-5.08_1x15_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-16-5.08_1x16_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-2-5.08_1x02_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-3-5.08_1x03_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-4-5.08_1x04_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-5-5.08_1x05_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-6-5.08_1x06_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-7-5.08_1x07_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-8-5.08_1x08_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-3-9-5.08_1x09_P5.08mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MPT-0,5-10-2.54_1x10_P2.54mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MPT-0,5-11-2.54_1x11_P2.54mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MPT-0,5-12-2.54_1x12_P2.54mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MPT-0,5-2-2.54_1x02_P2.54mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MPT-0,5-3-2.54_1x03_P2.54mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MPT-0,5-4-2.54_1x04_P2.54mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MPT-0,5-5-2.54_1x05_P2.54mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MPT-0,5-6-2.54_1x06_P2.54mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MPT-0,5-7-2.54_1x07_P2.54mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MPT-0,5-8-2.54_1x08_P2.54mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_MPT-0,5-9-2.54_1x09_P2.54mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-10-3.5-H_1x10_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-10-5.0-H_1x10_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-11-3.5-H_1x11_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-11-5.0-H_1x11_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-12-3.5-H_1x12_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-12-5.0-H_1x12_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-13-3.5-H_1x13_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-13-5.0-H_1x13_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-14-3.5-H_1x14_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-14-5.0-H_1x14_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-15-3.5-H_1x15_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-15-5.0-H_1x15_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-16-3.5-H_1x16_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-16-5.0-H_1x16_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-2-3.5-H_1x02_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-2-5.0-H_1x02_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-3-3.5-H_1x03_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-3-5.0-H_1x03_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-4-3.5-H_1x04_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-4-5.0-H_1x04_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-5-3.5-H_1x05_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-5-5.0-H_1x05_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-6-3.5-H_1x06_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-6-5.0-H_1x06_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-7-3.5-H_1x07_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-7-5.0-H_1x07_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-8-3.5-H_1x08_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-8-5.0-H_1x08_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-9-3.5-H_1x09_P3.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PT-1,5-9-5.0-H_1x09_P5.00mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-2-2,5-V-SMD_1x02-1MP_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-2-2.5-H-THR_1x02_P2.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-2-2.5-V-THR_1x02_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-2-HV-2.5-SMD_1x02-1MP_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-3-2,5-V-SMD_1x03-1MP_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-3-2.5-H-THR_1x03_P2.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-3-2.5-V-THR_1x03_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-3-HV-2.5-SMD_1x03-1MP_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-4-2,5-V-SMD_1x04-1MP_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-4-2.5-H-THR_1x04_P2.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-4-2.5-V-THR_1x04_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-4-HV-2.5-SMD_1x04-1MP_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-5-2,5-V-SMD_1x05-1MP_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-5-2.5-H-THR_1x05_P2.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-5-2.5-V-THR_1x05_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-5-HV-2.5-SMD_1x05-1MP_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-6-2,5-V-SMD_1x06-1MP_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-6-2.5-H-THR_1x06_P2.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-6-2.5-V-THR_1x06_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-6-HV-2.5-SMD_1x06-1MP_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-7-2,5-V-SMD_1x07-1MP_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-7-2.5-H-THR_1x07_P2.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-7-2.5-V-THR_1x07_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-7-HV-2.5-SMD_1x07-1MP_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-8-2,5-V-SMD_1x08-1MP_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-8-2.5-H-THR_1x08_P2.50mm_Horizontal +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-8-2.5-V-THR_1x08_P2.50mm_Vertical +TerminalBlock_Phoenix:TerminalBlock_Phoenix_PTSM-0,5-8-HV-2.5-SMD_1x08-1MP_P2.50mm_Vertical +TerminalBlock_RND:TerminalBlock_RND_205-00001_1x02_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00002_1x03_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00003_1x04_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00004_1x05_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00005_1x06_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00006_1x07_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00007_1x08_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00008_1x09_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00009_1x10_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00010_1x11_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00011_1x12_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00012_1x02_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00013_1x03_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00014_1x04_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00015_1x05_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00016_1x06_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00017_1x07_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00018_1x08_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00019_1x09_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00020_1x10_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00021_1x11_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00022_1x12_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00023_1x02_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00024_1x03_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00025_1x04_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00026_1x05_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00027_1x06_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00028_1x07_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00029_1x08_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00030_1x09_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00031_1x10_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00032_1x11_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00033_1x12_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00045_1x02_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00046_1x03_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00047_1x04_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00048_1x05_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00049_1x06_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00050_1x07_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00051_1x08_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00052_1x09_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00053_1x10_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00054_1x11_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00055_1x12_P5.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00056_1x02_P5.00mm_45Degree +TerminalBlock_RND:TerminalBlock_RND_205-00057_1x03_P5.00mm_45Degree +TerminalBlock_RND:TerminalBlock_RND_205-00058_1x04_P5.00mm_45Degree +TerminalBlock_RND:TerminalBlock_RND_205-00059_1x05_P5.00mm_45Degree +TerminalBlock_RND:TerminalBlock_RND_205-00060_1x06_P5.00mm_45Degree +TerminalBlock_RND:TerminalBlock_RND_205-00061_1x07_P5.00mm_45Degree +TerminalBlock_RND:TerminalBlock_RND_205-00062_1x08_P5.00mm_45Degree +TerminalBlock_RND:TerminalBlock_RND_205-00063_1x09_P5.00mm_45Degree +TerminalBlock_RND:TerminalBlock_RND_205-00064_1x10_P5.00mm_45Degree +TerminalBlock_RND:TerminalBlock_RND_205-00065_1x11_P5.00mm_45Degree +TerminalBlock_RND:TerminalBlock_RND_205-00066_1x12_P5.00mm_45Degree +TerminalBlock_RND:TerminalBlock_RND_205-00067_1x02_P7.50mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00068_1x03_P7.50mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00069_1x04_P7.50mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00070_1x05_P7.50mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00071_1x06_P7.50mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00072_1x07_P7.50mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00073_1x08_P7.50mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00074_1x09_P7.50mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00075_1x10_P7.50mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00076_1x11_P7.50mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00077_1x12_P7.50mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00078_1x02_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00079_1x03_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00080_1x04_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00081_1x05_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00082_1x06_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00083_1x07_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00084_1x08_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00085_1x09_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00086_1x10_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00087_1x11_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00088_1x12_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00232_1x02_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00233_1x03_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00234_1x04_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00235_1x05_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00236_1x06_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00237_1x07_P5.08mm_Horizontal +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 +TerminalBlock_RND:TerminalBlock_RND_205-00246_1x07_P10.16mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00247_1x08_P10.16mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00248_1x09_P10.16mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00249_1x10_P10.16mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00250_1x11_P10.16mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00251_1x12_P10.16mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00276_1x02_P5.00mm_Vertical +TerminalBlock_RND:TerminalBlock_RND_205-00277_1x03_P5.00mm_Vertical +TerminalBlock_RND:TerminalBlock_RND_205-00278_1x04_P5.00mm_Vertical +TerminalBlock_RND:TerminalBlock_RND_205-00279_1x05_P5.00mm_Vertical +TerminalBlock_RND:TerminalBlock_RND_205-00280_1x06_P5.00mm_Vertical +TerminalBlock_RND:TerminalBlock_RND_205-00281_1x07_P5.00mm_Vertical +TerminalBlock_RND:TerminalBlock_RND_205-00282_1x08_P5.00mm_Vertical +TerminalBlock_RND:TerminalBlock_RND_205-00283_1x09_P5.00mm_Vertical +TerminalBlock_RND:TerminalBlock_RND_205-00284_1x10_P5.00mm_Vertical +TerminalBlock_RND:TerminalBlock_RND_205-00285_1x11_P5.00mm_Vertical +TerminalBlock_RND:TerminalBlock_RND_205-00286_1x12_P5.00mm_Vertical +TerminalBlock_RND:TerminalBlock_RND_205-00287_1x02_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00288_1x03_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00289_1x04_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00290_1x05_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00291_1x06_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00292_1x07_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00293_1x08_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00294_1x09_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00295_1x10_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00296_1x11_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00297_1x12_P5.08mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00298_1x02_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00299_1x03_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00300_1x04_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00301_1x05_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00302_1x06_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00303_1x07_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00304_1x08_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00305_1x09_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00306_1x10_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00307_1x11_P10.00mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00308_1x12_P10.00mm_Horizontal +TerminalBlock_TE-Connectivity:TerminalBlock_TE_1-282834-0_1x10_P2.54mm_Horizontal +TerminalBlock_TE-Connectivity:TerminalBlock_TE_1-282834-1_1x11_P2.54mm_Horizontal +TerminalBlock_TE-Connectivity:TerminalBlock_TE_1-282834-2_1x12_P2.54mm_Horizontal +TerminalBlock_TE-Connectivity:TerminalBlock_TE_282834-2_1x02_P2.54mm_Horizontal +TerminalBlock_TE-Connectivity:TerminalBlock_TE_282834-3_1x03_P2.54mm_Horizontal +TerminalBlock_TE-Connectivity:TerminalBlock_TE_282834-4_1x04_P2.54mm_Horizontal +TerminalBlock_TE-Connectivity:TerminalBlock_TE_282834-5_1x05_P2.54mm_Horizontal +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 +TerminalBlock_WAGO:TerminalBlock_WAGO_236-104_1x04_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-105_1x05_P5.00mm_45Degree +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 +TerminalBlock_WAGO:TerminalBlock_WAGO_236-148_1x48_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-201_1x01_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-202_1x02_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-203_1x03_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-204_1x04_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-205_1x05_P7.50mm_45Degree +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 +TerminalBlock_WAGO:TerminalBlock_WAGO_236-302_1x02_P10.00mm_45Degree +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 +TerminalBlock_WAGO:TerminalBlock_WAGO_236-402_1x02_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-403_1x03_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-404_1x04_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-405_1x05_P5.00mm_45Degree +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 +TerminalBlock_WAGO:TerminalBlock_WAGO_236-448_1x48_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-501_1x01_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-502_1x02_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-503_1x03_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-504_1x04_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-505_1x05_P7.50mm_45Degree +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 +TerminalBlock_WAGO:TerminalBlock_WAGO_236-602_1x02_P10.00mm_45Degree +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 +TerminalBlock_WAGO:TerminalBlock_WAGO_804-102_1x02_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-103_1x03_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-104_1x04_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-105_1x05_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-106_1x06_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-107_1x07_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-108_1x08_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-109_1x09_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-110_1x10_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-111_1x11_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-112_1x12_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-113_1x13_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-114_1x14_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-115_1x15_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-116_1x16_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-124_1x24_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-301_1x01_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-302_1x02_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-303_1x03_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-304_1x04_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-305_1x05_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-306_1x06_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-307_1x07_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-308_1x08_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-309_1x09_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-310_1x10_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-311_1x11_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-312_1x12_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-316_1x16_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_804-324_1x24_P7.50mm_45Degree +TerminalBlock_Wuerth:Wuerth_REDCUBE-THR_WP-THRBU_74650073_THR +TerminalBlock_Wuerth:Wuerth_REDCUBE-THR_WP-THRBU_74650074_THR +TerminalBlock_Wuerth:Wuerth_REDCUBE-THR_WP-THRBU_74650094_THR +TerminalBlock_Wuerth:Wuerth_REDCUBE-THR_WP-THRBU_74650173_THR +TerminalBlock_Wuerth:Wuerth_REDCUBE-THR_WP-THRBU_74650174_THR +TerminalBlock_Wuerth:Wuerth_REDCUBE-THR_WP-THRBU_74650194_THR +TerminalBlock_Wuerth:Wuerth_REDCUBE-THR_WP-THRBU_74650195_THR +TerminalBlock_Wuerth:Wuerth_REDCUBE-THR_WP-THRBU_74655095_THR +TerminalBlock_Wuerth:Wuerth_REDCUBE-THR_WP-THRSH_74651173_THR +TerminalBlock_Wuerth:Wuerth_REDCUBE-THR_WP-THRSH_74651174_THR +TerminalBlock_Wuerth:Wuerth_REDCUBE-THR_WP-THRSH_74651175_THR +TerminalBlock_Wuerth:Wuerth_REDCUBE-THR_WP-THRSH_74651194_THR +TerminalBlock_Wuerth:Wuerth_REDCUBE-THR_WP-THRSH_74651195_THR +TestPoint:TestPoint_2Pads_Pitch2.54mm_Drill0.8mm +TestPoint:TestPoint_2Pads_Pitch5.08mm_Drill1.3mm +TestPoint:TestPoint_Bridge_Pitch2.0mm_Drill0.7mm +TestPoint:TestPoint_Bridge_Pitch2.54mm_Drill0.7mm +TestPoint:TestPoint_Bridge_Pitch2.54mm_Drill1.0mm +TestPoint:TestPoint_Bridge_Pitch2.54mm_Drill1.3mm +TestPoint:TestPoint_Bridge_Pitch3.81mm_Drill1.3mm +TestPoint:TestPoint_Bridge_Pitch5.08mm_Drill0.7mm +TestPoint:TestPoint_Bridge_Pitch5.08mm_Drill1.3mm +TestPoint:TestPoint_Bridge_Pitch6.35mm_Drill1.3mm +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_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 +TestPoint:TestPoint_Loop_D2.50mm_Drill1.85mm +TestPoint:TestPoint_Loop_D2.54mm_Drill1.5mm_Beaded +TestPoint:TestPoint_Loop_D2.60mm_Drill0.9mm_Beaded +TestPoint:TestPoint_Loop_D2.60mm_Drill1.4mm_Beaded +TestPoint:TestPoint_Loop_D2.60mm_Drill1.6mm_Beaded +TestPoint:TestPoint_Loop_D3.50mm_Drill0.9mm_Beaded +TestPoint:TestPoint_Loop_D3.50mm_Drill1.4mm_Beaded +TestPoint:TestPoint_Loop_D3.80mm_Drill2.0mm +TestPoint:TestPoint_Loop_D3.80mm_Drill2.5mm +TestPoint:TestPoint_Loop_D3.80mm_Drill2.8mm +TestPoint:TestPoint_Pad_1.0x1.0mm +TestPoint:TestPoint_Pad_1.5x1.5mm +TestPoint:TestPoint_Pad_2.0x2.0mm +TestPoint:TestPoint_Pad_2.5x2.5mm +TestPoint:TestPoint_Pad_3.0x3.0mm +TestPoint:TestPoint_Pad_4.0x4.0mm +TestPoint:TestPoint_Pad_D1.0mm +TestPoint:TestPoint_Pad_D1.5mm +TestPoint:TestPoint_Pad_D2.0mm +TestPoint:TestPoint_Pad_D2.5mm +TestPoint:TestPoint_Pad_D3.0mm +TestPoint:TestPoint_Pad_D4.0mm +TestPoint:TestPoint_Plated_Hole_D2.0mm +TestPoint:TestPoint_Plated_Hole_D3.0mm +TestPoint:TestPoint_Plated_Hole_D4.0mm +TestPoint:TestPoint_Plated_Hole_D5.0mm +TestPoint:TestPoint_THTPad_1.0x1.0mm_Drill0.5mm +TestPoint:TestPoint_THTPad_1.5x1.5mm_Drill0.7mm +TestPoint:TestPoint_THTPad_2.0x2.0mm_Drill1.0mm +TestPoint:TestPoint_THTPad_2.5x2.5mm_Drill1.2mm +TestPoint:TestPoint_THTPad_3.0x3.0mm_Drill1.5mm +TestPoint:TestPoint_THTPad_4.0x4.0mm_Drill2.0mm +TestPoint:TestPoint_THTPad_D1.0mm_Drill0.5mm +TestPoint:TestPoint_THTPad_D1.5mm_Drill0.7mm +TestPoint:TestPoint_THTPad_D2.0mm_Drill1.0mm +TestPoint:TestPoint_THTPad_D2.5mm_Drill1.2mm +TestPoint:TestPoint_THTPad_D3.0mm_Drill1.5mm +TestPoint:TestPoint_THTPad_D4.0mm_Drill2.0mm +Transformer_SMD:Pulse_P0926NL +Transformer_SMD:Pulse_PA1323NL +Transformer_SMD:Pulse_PA2001NL +Transformer_SMD:Pulse_PA2002NL-PA2008NL-PA2009NL +Transformer_SMD:Pulse_PA2004NL +Transformer_SMD:Pulse_PA2005NL +Transformer_SMD:Pulse_PA2006NL +Transformer_SMD:Pulse_PA2007NL +Transformer_SMD:Pulse_PA2777NL +Transformer_SMD:Pulse_PA3493NL +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 +Transformer_SMD:Transformer_Ethernet_Halo_N2_SO-16_7.11x12.7mm +Transformer_SMD:Transformer_Ethernet_Halo_N5_SO-16_7.11x12.7mm +Transformer_SMD:Transformer_Ethernet_Halo_N6_SO-16_7.11x14.73mm +Transformer_SMD:Transformer_Ethernet_HALO_TG111-MSC13 +Transformer_SMD:Transformer_Ethernet_Wuerth_749013011A +Transformer_SMD:Transformer_Ethernet_YDS_30F-51NL_SO-24_7.1x15.1mm +Transformer_SMD:Transformer_MACOM_SM-22 +Transformer_SMD:Transformer_MiniCircuits_AT224-1A +Transformer_SMD:Transformer_Murata_78250JC +Transformer_SMD:Transformer_NF_ETAL_P2781 +Transformer_SMD:Transformer_NF_ETAL_P2781_HandSoldering +Transformer_SMD:Transformer_NF_ETAL_P3000 +Transformer_SMD:Transformer_NF_ETAL_P3000_HandSoldering +Transformer_SMD:Transformer_NF_ETAL_P3181 +Transformer_SMD:Transformer_NF_ETAL_P3181_HandSoldering +Transformer_SMD:Transformer_NF_ETAL_P3188 +Transformer_SMD:Transformer_NF_ETAL_P3188_HandSoldering +Transformer_SMD:Transformer_NF_ETAL_P3191 +Transformer_SMD:Transformer_NF_ETAL_P3191_HandSoldering +Transformer_SMD:Transformer_Pulse_H1100NL +Transformer_SMD:Transformer_Wuerth_750315371 +Transformer_SMD:Transformer_Wurth_WE-AGDT-EP7 +Transformer_THT:Autotransformer_Toroid_1Tap_Horizontal_D10.5mm_Amidon-T37 +Transformer_THT:Autotransformer_Toroid_1Tap_Horizontal_D12.5mm_Amidon-T44 +Transformer_THT:Autotransformer_Toroid_1Tap_Horizontal_D14.0mm_Amidon-T50 +Transformer_THT:Autotransformer_Toroid_1Tap_Horizontal_D9.0mm_Amidon-T30 +Transformer_THT:Autotransformer_ZS1052-AC +Transformer_THT:Transformer_37x44 +Transformer_THT:Transformer_Breve_TEZ-22x24 +Transformer_THT:Transformer_Breve_TEZ-28x33 +Transformer_THT:Transformer_Breve_TEZ-35x42 +Transformer_THT:Transformer_Breve_TEZ-38x45 +Transformer_THT:Transformer_Breve_TEZ-44x52 +Transformer_THT:Transformer_Breve_TEZ-47x57 +Transformer_THT:Transformer_CHK_EI30-2VA_1xSec +Transformer_THT:Transformer_CHK_EI30-2VA_2xSec +Transformer_THT:Transformer_CHK_EI30-2VA_Neutral +Transformer_THT:Transformer_CHK_EI38-3VA_1xSec +Transformer_THT:Transformer_CHK_EI38-3VA_2xSec +Transformer_THT:Transformer_CHK_EI38-3VA_Neutral +Transformer_THT:Transformer_CHK_EI42-5VA_1xSec +Transformer_THT:Transformer_CHK_EI42-5VA_2xSec +Transformer_THT:Transformer_CHK_EI42-5VA_Neutral +Transformer_THT:Transformer_CHK_EI48-10VA_1xSec +Transformer_THT:Transformer_CHK_EI48-10VA_2xSec +Transformer_THT:Transformer_CHK_EI48-10VA_Neutral +Transformer_THT:Transformer_CHK_EI48-8VA_1xSec +Transformer_THT:Transformer_CHK_EI48-8VA_2xSec +Transformer_THT:Transformer_CHK_EI48-8VA_Neutral +Transformer_THT:Transformer_CHK_EI54-12VA_1xSec +Transformer_THT:Transformer_CHK_EI54-12VA_2xSec +Transformer_THT:Transformer_CHK_EI54-12VA_Neutral +Transformer_THT:Transformer_CHK_EI54-16VA_1xSec +Transformer_THT:Transformer_CHK_EI54-16VA_2xSec +Transformer_THT:Transformer_CHK_EI54-16VA_Neutral +Transformer_THT:Transformer_CHK_UI30-4VA_Flat +Transformer_THT:Transformer_CHK_UI39-10VA_Flat +Transformer_THT:Transformer_Coilcraft_Q4434-B_Rhombus-T1311 +Transformer_THT:Transformer_EPCOS_B66359A1013T_Horizontal +Transformer_THT:Transformer_EPCOS_B66359J1014T_Vertical +Transformer_THT:Transformer_Microphone_Lundahl_LL1538 +Transformer_THT:Transformer_Microphone_Lundahl_LL1587 +Transformer_THT:Transformer_Myrra_74040_Horizontal +Transformer_THT:Transformer_Myrra_EF20_7408x +Transformer_THT:Transformer_Myrra_EI30-5_44000_Horizontal +Transformer_THT:Transformer_NF_ETAL_1-1_P1200 +Transformer_THT:Transformer_NF_ETAL_P1165 +Transformer_THT:Transformer_NF_ETAL_P3324 +Transformer_THT:Transformer_NF_ETAL_P3356 +Transformer_THT:Transformer_Toroid_Horizontal_D10.5mm_Amidon-T37 +Transformer_THT:Transformer_Toroid_Horizontal_D12.5mm_Amidon-T44 +Transformer_THT:Transformer_Toroid_Horizontal_D14.0mm_Amidon-T50 +Transformer_THT:Transformer_Toroid_Horizontal_D18.0mm +Transformer_THT:Transformer_Toroid_Horizontal_D9.0mm_Amidon-T30 +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 +Transistor_Power_Module:Littelfuse_Package_H_XN2MM +Transistor_Power_Module:Littelfuse_Package_W_XBN2MM +Transistor_Power_Module:Littelfuse_Package_W_XN2MM +Transistor_Power_Module:ST_ACEPACK-2-CIB +Transistor_Power_Module:ST_ACEPACK-2-CIB_PressFIT +Transistor_Power_Module:ST_SDIP-25L +Valve:Valve_ECC-83-1 +Valve:Valve_ECC-83-2 +Valve:Valve_EURO +Valve:Valve_Glimm +Valve:Valve_Mini_G +Valve:Valve_Mini_P +Valve:Valve_Mini_Pentode_Linear +Valve:Valve_Noval_G +Valve:Valve_Noval_P +Valve:Valve_Octal +Varistor:RV_Disc_D12mm_W3.9mm_P7.5mm +Varistor:RV_Disc_D12mm_W4.2mm_P7.5mm +Varistor:RV_Disc_D12mm_W4.3mm_P7.5mm +Varistor:RV_Disc_D12mm_W4.4mm_P7.5mm +Varistor:RV_Disc_D12mm_W4.5mm_P7.5mm +Varistor:RV_Disc_D12mm_W4.6mm_P7.5mm +Varistor:RV_Disc_D12mm_W4.7mm_P7.5mm +Varistor:RV_Disc_D12mm_W4.8mm_P7.5mm +Varistor:RV_Disc_D12mm_W4mm_P7.5mm +Varistor:RV_Disc_D12mm_W5.1mm_P7.5mm +Varistor:RV_Disc_D12mm_W5.4mm_P7.5mm +Varistor:RV_Disc_D12mm_W5.8mm_P7.5mm +Varistor:RV_Disc_D12mm_W5mm_P7.5mm +Varistor:RV_Disc_D12mm_W6.1mm_P7.5mm +Varistor:RV_Disc_D12mm_W6.2mm_P7.5mm +Varistor:RV_Disc_D12mm_W6.3mm_P7.5mm +Varistor:RV_Disc_D12mm_W6.7mm_P7.5mm +Varistor:RV_Disc_D12mm_W7.1mm_P7.5mm +Varistor:RV_Disc_D12mm_W7.5mm_P7.5mm +Varistor:RV_Disc_D12mm_W7.9mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W11mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W3.9mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W4.2mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W4.3mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W4.4mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W4.5mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W4.6mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W4.7mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W4.8mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W4.9mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W4mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W5.2mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W5.4mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W5.9mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W5mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W6.1mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W6.3mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W6.4mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W6.8mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W7.2mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W7.5mm_P7.5mm +Varistor:RV_Disc_D15.5mm_W8mm_P7.5mm +Varistor:RV_Disc_D16.5mm_W6.7mm_P7.5mm +Varistor:RV_Disc_D21.5mm_W11.4mm_P10mm +Varistor:RV_Disc_D21.5mm_W4.3mm_P10mm +Varistor:RV_Disc_D21.5mm_W4.4mm_P10mm +Varistor:RV_Disc_D21.5mm_W4.5mm_P10mm +Varistor:RV_Disc_D21.5mm_W4.6mm_P10mm +Varistor:RV_Disc_D21.5mm_W4.7mm_P10mm +Varistor:RV_Disc_D21.5mm_W4.8mm_P10mm +Varistor:RV_Disc_D21.5mm_W4.9mm_P10mm +Varistor:RV_Disc_D21.5mm_W5.1mm_P10mm +Varistor:RV_Disc_D21.5mm_W5.3mm_P10mm +Varistor:RV_Disc_D21.5mm_W5.4mm_P10mm +Varistor:RV_Disc_D21.5mm_W5.6mm_P10mm +Varistor:RV_Disc_D21.5mm_W5.8mm_P10mm +Varistor:RV_Disc_D21.5mm_W5mm_P10mm +Varistor:RV_Disc_D21.5mm_W6.1mm_P7.5mm +Varistor:RV_Disc_D21.5mm_W6.3mm_P10mm +Varistor:RV_Disc_D21.5mm_W6.5mm_P10mm +Varistor:RV_Disc_D21.5mm_W6.7mm_P10mm +Varistor:RV_Disc_D21.5mm_W6.8mm_P10mm +Varistor:RV_Disc_D21.5mm_W7.1mm_P10mm +Varistor:RV_Disc_D21.5mm_W7.5mm_P10mm +Varistor:RV_Disc_D21.5mm_W7.9mm_P10mm +Varistor:RV_Disc_D21.5mm_W8.4mm_P10mm +Varistor:RV_Disc_D7mm_W3.4mm_P5mm +Varistor:RV_Disc_D7mm_W3.5mm_P5mm +Varistor:RV_Disc_D7mm_W3.6mm_P5mm +Varistor:RV_Disc_D7mm_W3.7mm_P5mm +Varistor:RV_Disc_D7mm_W3.8mm_P5mm +Varistor:RV_Disc_D7mm_W3.9mm_P5mm +Varistor:RV_Disc_D7mm_W4.2mm_P5mm +Varistor:RV_Disc_D7mm_W4.3mm_P5mm +Varistor:RV_Disc_D7mm_W4.5mm_P5mm +Varistor:RV_Disc_D7mm_W4.8mm_P5mm +Varistor:RV_Disc_D7mm_W4.9mm_P5mm +Varistor:RV_Disc_D7mm_W4mm_P5mm +Varistor:RV_Disc_D7mm_W5.1mm_P5mm +Varistor:RV_Disc_D7mm_W5.4mm_P5mm +Varistor:RV_Disc_D7mm_W5.5mm_P5mm +Varistor:RV_Disc_D7mm_W5.7mm_P5mm +Varistor:RV_Disc_D9mm_W3.3mm_P5mm +Varistor:RV_Disc_D9mm_W3.4mm_P5mm +Varistor:RV_Disc_D9mm_W3.5mm_P5mm +Varistor:RV_Disc_D9mm_W3.6mm_P5mm +Varistor:RV_Disc_D9mm_W3.7mm_P5mm +Varistor:RV_Disc_D9mm_W3.8mm_P5mm +Varistor:RV_Disc_D9mm_W3.9mm_P5mm +Varistor:RV_Disc_D9mm_W4.1mm_P5mm +Varistor:RV_Disc_D9mm_W4.2mm_P5mm +Varistor:RV_Disc_D9mm_W4.4mm_P5mm +Varistor:RV_Disc_D9mm_W4.5mm_P5mm +Varistor:RV_Disc_D9mm_W4.8mm_P5mm +Varistor:RV_Disc_D9mm_W4mm_P5mm +Varistor:RV_Disc_D9mm_W5.2mm_P5mm +Varistor:RV_Disc_D9mm_W5.4mm_P5mm +Varistor:RV_Disc_D9mm_W5.5mm_P5mm +Varistor:RV_Disc_D9mm_W5.7mm_P5mm +Varistor:RV_Disc_D9mm_W6.1mm_P5mm +Varistor:RV_Rect_V25S440P_L26.5mm_W8.2mm_P12.7mm +Varistor:Varistor_Panasonic_VF diff --git a/public/kicad/symbols.txt b/public/kicad/symbols.txt new file mode 100644 index 00000000..1cee1692 --- /dev/null +++ b/public/kicad/symbols.txt @@ -0,0 +1,21807 @@ +# This file contains all the KiCad symbols available in the official library +# Generated by symbols.sh +# on Sun Feb 16 21:42:01 CET 2025 +4xxx:14528 +4xxx:14529 +4xxx:14538 +4xxx:4001 +4xxx:4002 +4xxx:4009 +4xxx:4010 +4xxx:40106 +4xxx:4011 +4xxx:4012 +4xxx:4013 +4xxx:4016 +4xxx:4017 +4xxx:4020 +4xxx:4021 +4xxx:4022 +4xxx:4023 +4xxx:4025 +4xxx:4027 +4xxx:4028 +4xxx:4029 +4xxx:4040 +4xxx:4046 +4xxx:4047 +4xxx:4049 +4xxx:4050 +4xxx:4051 +4xxx:4052 +4xxx:4053 +4xxx:4056 +4xxx:4060 +4xxx:4066 +4xxx:4069 +4xxx:4070 +4xxx:4071 +4xxx:4072 +4xxx:4073 +4xxx:4075 +4xxx:4077 +4xxx:4081 +4xxx:4098 +4xxx:4504 +4xxx:4510 +4xxx:4518 +4xxx:4520 +4xxx:4528 +4xxx:4538 +4xxx:4543 +4xxx:CD4033B +4xxx:HEF4093B +4xxx:HEF4094B +4xxx_IEEE:4001 +4xxx_IEEE:4002 +4xxx_IEEE:4006 +4xxx_IEEE:4008 +4xxx_IEEE:4009 +4xxx_IEEE:4010 +4xxx_IEEE:40104 +4xxx_IEEE:40106 +4xxx_IEEE:4011 +4xxx_IEEE:40110 +4xxx_IEEE:4012 +4xxx_IEEE:4013 +4xxx_IEEE:4014 +4xxx_IEEE:4015 +4xxx_IEEE:4016 +4xxx_IEEE:40160 +4xxx_IEEE:40161 +4xxx_IEEE:40162 +4xxx_IEEE:40163 +4xxx_IEEE:4017 +4xxx_IEEE:40174 +4xxx_IEEE:40175 +4xxx_IEEE:4018 +4xxx_IEEE:4019 +4xxx_IEEE:40192 +4xxx_IEEE:40193 +4xxx_IEEE:40194 +4xxx_IEEE:4020 +4xxx_IEEE:4021 +4xxx_IEEE:4022 +4xxx_IEEE:4023 +4xxx_IEEE:4024 +4xxx_IEEE:40240 +4xxx_IEEE:40244 +4xxx_IEEE:40245 +4xxx_IEEE:4025 +4xxx_IEEE:4027 +4xxx_IEEE:4028 +4xxx_IEEE:4029 +4xxx_IEEE:4030 +4xxx_IEEE:40373 +4xxx_IEEE:40374 +4xxx_IEEE:4040 +4xxx_IEEE:4041 +4xxx_IEEE:4042 +4xxx_IEEE:4043 +4xxx_IEEE:4044 +4xxx_IEEE:4046 +4xxx_IEEE:4048 +4xxx_IEEE:4049 +4xxx_IEEE:4050 +4xxx_IEEE:4051 +4xxx_IEEE:4052 +4xxx_IEEE:4053 +4xxx_IEEE:4060 +4xxx_IEEE:4066 +4xxx_IEEE:4068 +4xxx_IEEE:4069 +4xxx_IEEE:4070 +4xxx_IEEE:4071 +4xxx_IEEE:4072 +4xxx_IEEE:4073 +4xxx_IEEE:4075 +4xxx_IEEE:4077 +4xxx_IEEE:4078 +4xxx_IEEE:4081 +4xxx_IEEE:4082 +4xxx_IEEE:4093 +4xxx_IEEE:4095 +4xxx_IEEE:4096 +4xxx_IEEE:4099 +4xxx_IEEE:4104 +4xxx_IEEE:4160 +4xxx_IEEE:4161 +4xxx_IEEE:4162 +4xxx_IEEE:4163 +4xxx_IEEE:4174 +4xxx_IEEE:4175 +4xxx_IEEE:4502 +4xxx_IEEE:4504 +4xxx_IEEE:4507 +4xxx_IEEE:4508 +4xxx_IEEE:4510 +4xxx_IEEE:4511 +4xxx_IEEE:4512 +4xxx_IEEE:4514 +4xxx_IEEE:4515 +4xxx_IEEE:4518 +4xxx_IEEE:4520 +4xxx_IEEE:4528 +4xxx_IEEE:4529 +4xxx_IEEE:4530 +4xxx_IEEE:4538 +4xxx_IEEE:4539 +4xxx_IEEE:4543 +4xxx_IEEE:4555 +4xxx_IEEE:4556 +4xxx_IEEE:4584 +4xxx_IEEE:4585 +74xGxx:74AHC1G00 +74xGxx:74AHC1G02 +74xGxx:74AHC1G04 +74xGxx:74AHC1G08 +74xGxx:74AHC1G125 +74xGxx:74AHC1G126 +74xGxx:74AHC1G14 +74xGxx:74AHC1G32 +74xGxx:74AHC1G4210 +74xGxx:74AHC1G86 +74xGxx:74AHC1GU04 +74xGxx:74AHC2G00 +74xGxx:74AHCT1G00 +74xGxx:74AHCT1G02 +74xGxx:74AHCT1G04 +74xGxx:74AHCT1G08 +74xGxx:74AHCT1G125 +74xGxx:74AHCT1G126 +74xGxx:74AHCT1G14 +74xGxx:74AHCT1G32 +74xGxx:74AHCT1G86 +74xGxx:74AHCT1GU04 +74xGxx:74AHCT2G00 +74xGxx:74AUC1G00 +74xGxx:74AUC1G02 +74xGxx:74AUC1G04 +74xGxx:74AUC1G06 +74xGxx:74AUC1G07 +74xGxx:74AUC1G08 +74xGxx:74AUC1G125 +74xGxx:74AUC1G126 +74xGxx:74AUC1G14 +74xGxx:74AUC1G17 +74xGxx:74AUC1G18 +74xGxx:74AUC1G19 +74xGxx:74AUC1G240 +74xGxx:74AUC1G32 +74xGxx:74AUC1G66 +74xGxx:74AUC1G74 +74xGxx:74AUC1G79 +74xGxx:74AUC1G80 +74xGxx:74AUC1G86 +74xGxx:74AUC1GU04 +74xGxx:74AUC2G00 +74xGxx:74AUC2G02 +74xGxx:74AUC2G04 +74xGxx:74AUC2G06 +74xGxx:74AUC2G07 +74xGxx:74AUC2G08 +74xGxx:74AUC2G125 +74xGxx:74AUC2G126 +74xGxx:74AUC2G240 +74xGxx:74AUC2G241 +74xGxx:74AUC2G32 +74xGxx:74AUC2G34 +74xGxx:74AUC2G53 +74xGxx:74AUC2G66 +74xGxx:74AUC2G79 +74xGxx:74AUC2G80 +74xGxx:74AUC2G86 +74xGxx:74AUC2GU04 +74xGxx:74AUP1G00 +74xGxx:74AUP1G02 +74xGxx:74AUP1G04 +74xGxx:74AUP1G06 +74xGxx:74AUP1G07 +74xGxx:74AUP1G08 +74xGxx:74AUP1G125 +74xGxx:74AUP1G126 +74xGxx:74AUP1G14 +74xGxx:74AUP1G17 +74xGxx:74AUP1G240 +74xGxx:74AUP1G32 +74xGxx:74AUP1G34 +74xGxx:74AUP1G57 +74xGxx:74AUP1G58 +74xGxx:74AUP1G74 +74xGxx:74AUP1G79 +74xGxx:74AUP1G80 +74xGxx:74AUP1G97 +74xGxx:74AUP1G98 +74xGxx:74AUP1G99 +74xGxx:74AUP1GU04 +74xGxx:74CB3T1G125 +74xGxx:74CBT1G125 +74xGxx:74CBT1G384 +74xGxx:74CBTD1G125 +74xGxx:74CBTD1G384 +74xGxx:74CBTLV1G125 +74xGxx:74LVC1G00 +74xGxx:74LVC1G02 +74xGxx:74LVC1G04 +74xGxx:74LVC1G06 +74xGxx:74LVC1G07 +74xGxx:74LVC1G08 +74xGxx:74LVC1G0832 +74xGxx:74LVC1G10 +74xGxx:74LVC1G11 +74xGxx:74LVC1G123 +74xGxx:74LVC1G125 +74xGxx:74LVC1G126 +74xGxx:74LVC1G139 +74xGxx:74LVC1G14 +74xGxx:74LVC1G17 +74xGxx:74LVC1G175 +74xGxx:74LVC1G18 +74xGxx:74LVC1G19 +74xGxx:74LVC1G240 +74xGxx:74LVC1G27 +74xGxx:74LVC1G29 +74xGxx:74LVC1G3157 +74xGxx:74LVC1G32 +74xGxx:74LVC1G3208 +74xGxx:74LVC1G332 +74xGxx:74LVC1G34 +74xGxx:74LVC1G373 +74xGxx:74LVC1G374 +74xGxx:74LVC1G38 +74xGxx:74LVC1G386 +74xGxx:74LVC1G57 +74xGxx:74LVC1G58 +74xGxx:74LVC1G66 +74xGxx:74LVC1G79 +74xGxx:74LVC1G80 +74xGxx:74LVC1G86 +74xGxx:74LVC1G97 +74xGxx:74LVC1G98 +74xGxx:74LVC1G99 +74xGxx:74LVC1GU04 +74xGxx:74LVC1GU04DRL +74xGxx:74LVC2G00 +74xGxx:74LVC2G02 +74xGxx:74LVC2G04 +74xGxx:74LVC2G06 +74xGxx:74LVC2G07 +74xGxx:74LVC2G08 +74xGxx:74LVC2G125 +74xGxx:74LVC2G126 +74xGxx:74LVC2G14 +74xGxx:74LVC2G157 +74xGxx:74LVC2G17 +74xGxx:74LVC2G240 +74xGxx:74LVC2G241 +74xGxx:74LVC2G32 +74xGxx:74LVC2G34 +74xGxx:74LVC2G38 +74xGxx:74LVC2G53 +74xGxx:74LVC2G66 +74xGxx:74LVC2G74 +74xGxx:74LVC2G79 +74xGxx:74LVC2G80 +74xGxx:74LVC2G86 +74xGxx:74LVC2GU04 +74xGxx:74LVC3G04 +74xGxx:74LVC3G06 +74xGxx:74LVC3G07 +74xGxx:74LVC3G14 +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 +74xx:74HC00 +74xx:74HC02 +74xx:74HC04 +74xx:74HC123 +74xx:74HC137 +74xx:74HC138 +74xx:74HC14 +74xx:74HC164 +74xx:74HC165 +74xx:74HC173 +74xx:74HC192 +74xx:74HC193 +74xx:74HC237 +74xx:74HC238 +74xx:74HC240 +74xx:74HC244 +74xx:74HC245 +74xx:74HC273 +74xx:74HC373 +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 +74xx:74HCT04 +74xx:74HCT123 +74xx:74HCT137 +74xx:74HCT138 +74xx:74HCT164 +74xx:74HCT173 +74xx:74HCT193 +74xx:74HCT237 +74xx:74HCT238 +74xx:74HCT240 +74xx:74HCT244 +74xx:74HCT273 +74xx:74HCT373 +74xx:74HCT374 +74xx:74HCT4051 +74xx:74HCT541 +74xx:74HCT574 +74xx:74HCT595 +74xx:74HCT596 +74xx:74HCT688 +74xx:74HCT74 +74xx:74HCT85 +74xx:74LCX07 +74xx:74LS00 +74xx:74LS01 +74xx:74LS02 +74xx:74LS03 +74xx:74LS04 +74xx:74LS05 +74xx:74LS06 +74xx:74LS06N +74xx:74LS07 +74xx:74LS08 +74xx:74LS09 +74xx:74LS10 +74xx:74LS107 +74xx:74LS109 +74xx:74LS11 +74xx:74LS112 +74xx:74LS113 +74xx:74LS114 +74xx:74LS12 +74xx:74LS121 +74xx:74LS122 +74xx:74LS123 +74xx:74LS125 +74xx:74LS126 +74xx:74LS13 +74xx:74LS132 +74xx:74LS133 +74xx:74LS136 +74xx:74LS137 +74xx:74LS138 +74xx:74LS139 +74xx:74LS14 +74xx:74LS145 +74xx:74LS147 +74xx:74LS148 +74xx:74LS15 +74xx:74LS151 +74xx:74LS153 +74xx:74LS154 +74xx:74LS155 +74xx:74LS156 +74xx:74LS157 +74xx:74LS158 +74xx:74LS160 +74xx:74LS161 +74xx:74LS162 +74xx:74LS163 +74xx:74LS165 +74xx:74LS166 +74xx:74LS168 +74xx:74LS169 +74xx:74LS170 +74xx:74LS173 +74xx:74LS174 +74xx:74LS175 +74xx:74LS181 +74xx:74LS182 +74xx:74LS190 +74xx:74LS191 +74xx:74LS192 +74xx:74LS193 +74xx:74LS194 +74xx:74LS195 +74xx:74LS196 +74xx:74LS197 +74xx:74LS20 +74xx:74LS21 +74xx:74LS22 +74xx:74LS221 +74xx:74LS240 +74xx:74LS240_Split +74xx:74LS241 +74xx:74LS241_Split +74xx:74LS242 +74xx:74LS243 +74xx:74LS244 +74xx:74LS244_Split +74xx:74LS245 +74xx:74LS246 +74xx:74LS247 +74xx:74LS248 +74xx:74LS249 +74xx:74LS251 +74xx:74LS253 +74xx:74LS256 +74xx:74LS257 +74xx:74LS258 +74xx:74LS259 +74xx:74LS26 +74xx:74LS27 +74xx:74LS273 +74xx:74LS279 +74xx:74LS28 +74xx:74LS280 +74xx:74LS283 +74xx:74LS290 +74xx:74LS293 +74xx:74LS295 +74xx:74LS298 +74xx:74LS299 +74xx:74LS30 +74xx:74LS32 +74xx:74LS322 +74xx:74LS323 +74xx:74LS33 +74xx:74LS348 +74xx:74LS352 +74xx:74LS353 +74xx:74LS365 +74xx:74LS366 +74xx:74LS367 +74xx:74LS368 +74xx:74LS37 +74xx:74LS373 +74xx:74LS374 +74xx:74LS375 +74xx:74LS377 +74xx:74LS378 +74xx:74LS379 +74xx:74LS38 +74xx:74LS385 +74xx:74LS386 +74xx:74LS390 +74xx:74LS393 +74xx:74LS395 +74xx:74LS398 +74xx:74LS399 +74xx:74LS40 +74xx:74LS42 +74xx:74LS46 +74xx:74LS47 +74xx:74LS48 +74xx:74LS49 +74xx:74LS51 +74xx:74LS540 +74xx:74LS541 +74xx:74LS54N +74xx:74LS55 +74xx:74LS573 +74xx:74LS574 +74xx:74LS590 +74xx:74LS595 +74xx:74LS596 +74xx:74LS629 +74xx:74LS670 +74xx:74LS688 +74xx:74LS73 +74xx:74LS74 +74xx:74LS75 +74xx:74LS76 +74xx:74LS77 +74xx:74LS78 +74xx:74LS83 +74xx:74LS85 +74xx:74LS86 +74xx:74LS90 +74xx:74LS91 +74xx:74LS92 +74xx:74LS93 +74xx:74LS95 +74xx:74LV14 +74xx:74LV8154 +74xx:74LVC125 +74xx:74VHC9164FT +74xx:CD74AC238 +74xx:CD74HC4067M +74xx:CD74HC4067SM +74xx:MC74LCX16245DT +74xx:MM74C923 +74xx:SN74ALVC164245DGG +74xx:SN74ALVC164245DL +74xx:SN74AVC16827DGGR +74xx:SN74CB3Q3384ADBQ +74xx:SN74CB3Q3384APW +74xx:SN74LS07 +74xx:SN74LS07N +74xx:SN74LV4T125 +74xx_IEEE:7400 +74xx_IEEE:7401 +74xx_IEEE:7402 +74xx_IEEE:7403 +74xx_IEEE:7404 +74xx_IEEE:7405 +74xx_IEEE:7406 +74xx_IEEE:7407 +74xx_IEEE:7408 +74xx_IEEE:7409 +74xx_IEEE:7410 +74xx_IEEE:7411 +74xx_IEEE:7412 +74xx_IEEE:74125 +74xx_IEEE:74126 +74xx_IEEE:74128 +74xx_IEEE:7413 +74xx_IEEE:74132 +74xx_IEEE:74136 +74xx_IEEE:7414 +74xx_IEEE:74141 +74xx_IEEE:74145 +74xx_IEEE:74147 +74xx_IEEE:74148 +74xx_IEEE:74151 +74xx_IEEE:74153 +74xx_IEEE:74154 +74xx_IEEE:74155 +74xx_IEEE:74156 +74xx_IEEE:74157 +74xx_IEEE:74158 +74xx_IEEE:74159 +74xx_IEEE:7416 +74xx_IEEE:74164 +74xx_IEEE:74165 +74xx_IEEE:74166 +74xx_IEEE:7417 +74xx_IEEE:74173 +74xx_IEEE:74176 +74xx_IEEE:74196 +74xx_IEEE:7420 +74xx_IEEE:7421 +74xx_IEEE:7422 +74xx_IEEE:74246 +74xx_IEEE:74247 +74xx_IEEE:74248 +74xx_IEEE:74249 +74xx_IEEE:7425 +74xx_IEEE:74251 +74xx_IEEE:74253 +74xx_IEEE:7426 +74xx_IEEE:7427 +74xx_IEEE:74278 +74xx_IEEE:7428 +74xx_IEEE:74293 +74xx_IEEE:7430 +74xx_IEEE:7432 +74xx_IEEE:7433 +74xx_IEEE:7437 +74xx_IEEE:7438 +74xx_IEEE:7439 +74xx_IEEE:7440 +74xx_IEEE:7442 +74xx_IEEE:74425 +74xx_IEEE:74426 +74xx_IEEE:7443 +74xx_IEEE:7444 +74xx_IEEE:7445 +74xx_IEEE:7446 +74xx_IEEE:7447 +74xx_IEEE:7448 +74xx_IEEE:7451 +74xx_IEEE:7454 +74xx_IEEE:7483 +74xx_IEEE:7485 +74xx_IEEE:7486 +74xx_IEEE:7490 +74xx_IEEE:7491 +74xx_IEEE:7492 +74xx_IEEE:7493 +74xx_IEEE:7495 +74xx_IEEE:7496 +74xx_IEEE:74HC237 +74xx_IEEE:74HC238 +74xx_IEEE:74HC36 +74xx_IEEE:74HC804 +74xx_IEEE:74HC805 +74xx_IEEE:74HC808 +74xx_IEEE:74HC832 +74xx_IEEE:74LS133 +74xx_IEEE:74LS137 +74xx_IEEE:74LS138 +74xx_IEEE:74LS139 +74xx_IEEE:74LS15 +74xx_IEEE:74LS152 +74xx_IEEE:74LS160 +74xx_IEEE:74LS161 +74xx_IEEE:74LS162 +74xx_IEEE:74LS163 +74xx_IEEE:74LS168 +74xx_IEEE:74LS169 +74xx_IEEE:74LS170 +74xx_IEEE:74LS177 +74xx_IEEE:74LS18 +74xx_IEEE:74LS19 +74xx_IEEE:74LS190 +74xx_IEEE:74LS191 +74xx_IEEE:74LS192 +74xx_IEEE:74LS193 +74xx_IEEE:74LS194 +74xx_IEEE:74LS195 +74xx_IEEE:74LS197 +74xx_IEEE:74LS239 +74xx_IEEE:74LS24 +74xx_IEEE:74LS240 +74xx_IEEE:74LS241 +74xx_IEEE:74LS242 +74xx_IEEE:74LS243 +74xx_IEEE:74LS244 +74xx_IEEE:74LS245 +74xx_IEEE:74LS257 +74xx_IEEE:74LS258 +74xx_IEEE:74LS266 +74xx_IEEE:74LS280 +74xx_IEEE:74LS283 +74xx_IEEE:74LS290 +74xx_IEEE:74LS295 +74xx_IEEE:74LS298 +74xx_IEEE:74LS299 +74xx_IEEE:74LS323 +74xx_IEEE:74LS347 +74xx_IEEE:74LS348 +74xx_IEEE:74LS352 +74xx_IEEE:74LS353 +74xx_IEEE:74LS365 +74xx_IEEE:74LS366 +74xx_IEEE:74LS367 +74xx_IEEE:74LS368 +74xx_IEEE:74LS386 +74xx_IEEE:74LS390 +74xx_IEEE:74LS395 +74xx_IEEE:74LS396 +74xx_IEEE:74LS398 +74xx_IEEE:74LS399 +74xx_IEEE:74LS445 +74xx_IEEE:74LS447 +74xx_IEEE:74LS465 +74xx_IEEE:74LS466 +74xx_IEEE:74LS467 +74xx_IEEE:74LS468 +74xx_IEEE:74LS49 +74xx_IEEE:74LS540 +74xx_IEEE:74LS541 +74xx_IEEE:74LS55 +74xx_IEEE:74LS56 +74xx_IEEE:74LS57 +74xx_IEEE:74LS590 +74xx_IEEE:74LS591 +74xx_IEEE:74LS594 +74xx_IEEE:74LS595 +74xx_IEEE:74LS596 +74xx_IEEE:74LS597 +74xx_IEEE:74LS599 +74xx_IEEE:74LS620 +74xx_IEEE:74LS621 +74xx_IEEE:74LS622 +74xx_IEEE:74LS623 +74xx_IEEE:74LS638 +74xx_IEEE:74LS639 +74xx_IEEE:74LS640 +74xx_IEEE:74LS641 +74xx_IEEE:74LS642 +74xx_IEEE:74LS645 +74xx_IEEE:74LS668 +74xx_IEEE:74LS669 +74xx_IEEE:74LS670 +74xx_IEEE:74LS682 +74xx_IEEE:74LS683 +74xx_IEEE:74LS684 +74xx_IEEE:74LS685 +74xx_IEEE:74LS686 +74xx_IEEE:74LS687 +74xx_IEEE:74LS688 +74xx_IEEE:74LS689 +74xx_IEEE:74S140 +Amplifier_Audio:IR4301 +Amplifier_Audio:IR4302 +Amplifier_Audio:IR4311 +Amplifier_Audio:IR4312 +Amplifier_Audio:IR4321 +Amplifier_Audio:IR4322 +Amplifier_Audio:IRS2052M +Amplifier_Audio:IRS2092 +Amplifier_Audio:IRS2092S +Amplifier_Audio:IRS2093M +Amplifier_Audio:IRS20957S +Amplifier_Audio:IRS20965S +Amplifier_Audio:IRS2452AM +Amplifier_Audio:IS31AP4991-GRLS2 +Amplifier_Audio:IS31AP4991-SLS2 +Amplifier_Audio:LM1875 +Amplifier_Audio:LM1876 +Amplifier_Audio:LM1877 +Amplifier_Audio:LM2876 +Amplifier_Audio:LM380N +Amplifier_Audio:LM380N-8 +Amplifier_Audio:LM384 +Amplifier_Audio:LM386 +Amplifier_Audio:LM3886 +Amplifier_Audio:LM4752TS +Amplifier_Audio:LM4755TS +Amplifier_Audio:LM4766 +Amplifier_Audio:LM4810 +Amplifier_Audio:LM4811 +Amplifier_Audio:LM4950TA +Amplifier_Audio:LM4950TS +Amplifier_Audio:LM4990ITL +Amplifier_Audio:LM4990LD +Amplifier_Audio:LM4990MH +Amplifier_Audio:LM4990MM +Amplifier_Audio:LME49600 +Amplifier_Audio:MA12040 +Amplifier_Audio:MA12040P +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 +Amplifier_Audio:PAM8302AAD +Amplifier_Audio:PAM8302AAS +Amplifier_Audio:PAM8302AAY +Amplifier_Audio:PAM8403D +Amplifier_Audio:PAM8406D +Amplifier_Audio:SSM2017P +Amplifier_Audio:SSM2018 +Amplifier_Audio:SSM2120 +Amplifier_Audio:SSM2122 +Amplifier_Audio:SSM2165 +Amplifier_Audio:SSM2167 +Amplifier_Audio:SSM2211CP +Amplifier_Audio:SSM2211S +Amplifier_Audio:STK433_Sanyo +Amplifier_Audio:STK435_Sanyo +Amplifier_Audio:STK436_Sanyo +Amplifier_Audio:STK437_Sanyo +Amplifier_Audio:STK439_Sanyo +Amplifier_Audio:STK441_Sanyo +Amplifier_Audio:STK443_Sanyo +Amplifier_Audio:Si8241BB +Amplifier_Audio:Si8241CB +Amplifier_Audio:Si8244BB +Amplifier_Audio:Si8244CB +Amplifier_Audio:TAS5825MRHB +Amplifier_Audio:TDA1308 +Amplifier_Audio:TDA2003 +Amplifier_Audio:TDA2005 +Amplifier_Audio:TDA2030 +Amplifier_Audio:TDA2050 +Amplifier_Audio:TDA7052A +Amplifier_Audio:TDA7264 +Amplifier_Audio:TDA7265 +Amplifier_Audio:TDA7265B +Amplifier_Audio:TDA7266 +Amplifier_Audio:TDA7266D +Amplifier_Audio:TDA7266M +Amplifier_Audio:TDA7266P +Amplifier_Audio:TDA7269A +Amplifier_Audio:TDA7292 +Amplifier_Audio:TDA7293 +Amplifier_Audio:TDA7294 +Amplifier_Audio:TDA7295 +Amplifier_Audio:TDA7296 +Amplifier_Audio:TDA7297 +Amplifier_Audio:TDA7496 +Amplifier_Audio:TFA9879HN +Amplifier_Audio:THAT151xx08 +Amplifier_Audio:THAT2180 +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 +Amplifier_Buffer:LM6321M +Amplifier_Buffer:LM6321N +Amplifier_Current:AD8202 +Amplifier_Current:AD8203 +Amplifier_Current:AD8205 +Amplifier_Current:AD8206 +Amplifier_Current:AD8208 +Amplifier_Current:AD8209 +Amplifier_Current:AD8210 +Amplifier_Current:AD8211 +Amplifier_Current:AD8212 +Amplifier_Current:AD8213 +Amplifier_Current:AD8215 +Amplifier_Current:AD8216 +Amplifier_Current:AD8217 +Amplifier_Current:AD8218xCP +Amplifier_Current:AD8218xRM +Amplifier_Current:AD8219 +Amplifier_Current:AD8417 +Amplifier_Current:AD8418 +Amplifier_Current:BQ500100DCK +Amplifier_Current:INA138 +Amplifier_Current:INA139 +Amplifier_Current:INA168 +Amplifier_Current:INA169 +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 +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 +Amplifier_Current:INA240A2D +Amplifier_Current:INA240A2PW +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 +Amplifier_Current:LTC6102HVxMS8 +Amplifier_Current:LTC6102xDD +Amplifier_Current:LTC6102xDD-1 +Amplifier_Current:LTC6102xMS8 +Amplifier_Current:LTC6102xMS8-1 +Amplifier_Current:MAX4080F +Amplifier_Current:MAX4080S +Amplifier_Current:MAX4080T +Amplifier_Current:MAX4081F +Amplifier_Current:MAX4081S +Amplifier_Current:MAX4081T +Amplifier_Current:MAX471 +Amplifier_Current:MAX472 +Amplifier_Current:NCS210 +Amplifier_Current:NCS211 +Amplifier_Current:NCS213 +Amplifier_Current:NCS214 +Amplifier_Current:NCV210 +Amplifier_Current:NCV211 +Amplifier_Current:NCV213 +Amplifier_Current:NCV214 +Amplifier_Current:ZXCT1009F +Amplifier_Current:ZXCT1009T8 +Amplifier_Current:ZXCT1010 +Amplifier_Current:ZXCT1107 +Amplifier_Current:ZXCT1109 +Amplifier_Current:ZXCT1110 +Amplifier_Difference:AD628 +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 +Amplifier_Instrumentation:AD623AN +Amplifier_Instrumentation:AD623ANZ +Amplifier_Instrumentation:AD623AR +Amplifier_Instrumentation:AD623ARM +Amplifier_Instrumentation:AD623ARMZ +Amplifier_Instrumentation:AD623ARZ +Amplifier_Instrumentation:AD623BN +Amplifier_Instrumentation:AD623BNZ +Amplifier_Instrumentation:AD623BR +Amplifier_Instrumentation:AD623BRZ +Amplifier_Instrumentation:AD8230 +Amplifier_Instrumentation:AD8231 +Amplifier_Instrumentation:AD8236 +Amplifier_Instrumentation:AD8236ARMZ +Amplifier_Instrumentation:AD8421 +Amplifier_Instrumentation:AD8421ARMZ +Amplifier_Instrumentation:AD8421ARZ +Amplifier_Instrumentation:AD8421BRMZ +Amplifier_Instrumentation:AD8421BRZ +Amplifier_Instrumentation:AD8422 +Amplifier_Instrumentation:AD8422ARMZ +Amplifier_Instrumentation:AD8422ARZ +Amplifier_Instrumentation:AD8422BRMZ +Amplifier_Instrumentation:AD8422BRZ +Amplifier_Instrumentation:AD8429 +Amplifier_Instrumentation:AD8429ARZ +Amplifier_Instrumentation:AD8429BRZ +Amplifier_Instrumentation:INA128 +Amplifier_Instrumentation:INA129 +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 +Amplifier_Operational:AD8620xRM +Amplifier_Operational:AD8655 +Amplifier_Operational:AD8656 +Amplifier_Operational:AD8676xR +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 +Amplifier_Operational:ADA4610-2xCP +Amplifier_Operational:ADA4610-2xR +Amplifier_Operational:ADA4610-2xRM +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-4ARUZ +Amplifier_Operational:ADA4817-1ACP +Amplifier_Operational:ADA4817-1ARD +Amplifier_Operational:ADA4817-2ACP +Amplifier_Operational:ADA4841-1YRJ +Amplifier_Operational:ADA4870ARRZ +Amplifier_Operational:ADA4898-1YRDZ +Amplifier_Operational:ADA4898-2 +Amplifier_Operational:AS13704 +Amplifier_Operational:CA3080 +Amplifier_Operational:CA3080A +Amplifier_Operational:CA3130 +Amplifier_Operational:CA3140 +Amplifier_Operational:HMC799LP3E +Amplifier_Operational:L272 +Amplifier_Operational:L272D +Amplifier_Operational:L272M +Amplifier_Operational:LF155 +Amplifier_Operational:LF156 +Amplifier_Operational:LF256 +Amplifier_Operational:LF257 +Amplifier_Operational:LF351D +Amplifier_Operational:LF351N +Amplifier_Operational:LF355 +Amplifier_Operational:LF356 +Amplifier_Operational:LF357 +Amplifier_Operational:LM101 +Amplifier_Operational:LM13600 +Amplifier_Operational:LM13700 +Amplifier_Operational:LM201 +Amplifier_Operational:LM2902 +Amplifier_Operational:LM2904 +Amplifier_Operational:LM301 +Amplifier_Operational:LM318H +Amplifier_Operational:LM318J +Amplifier_Operational:LM318M +Amplifier_Operational:LM318N +Amplifier_Operational:LM321 +Amplifier_Operational:LM324 +Amplifier_Operational:LM324A +Amplifier_Operational:LM358 +Amplifier_Operational:LM358_DFN +Amplifier_Operational:LM4250 +Amplifier_Operational:LM4562 +Amplifier_Operational:LM6142xIx +Amplifier_Operational:LM6144xIx +Amplifier_Operational:LM6171D +Amplifier_Operational:LM6171xxN +Amplifier_Operational:LM6172 +Amplifier_Operational:LM6361 +Amplifier_Operational:LM675 +Amplifier_Operational:LM7171xIM +Amplifier_Operational:LM7171xIN +Amplifier_Operational:LM7332 +Amplifier_Operational:LM741 +Amplifier_Operational:LM8261 +Amplifier_Operational:LMC6062 +Amplifier_Operational:LMC6082 +Amplifier_Operational:LMC6482 +Amplifier_Operational:LMC6484 +Amplifier_Operational:LMH6551MA +Amplifier_Operational:LMH6551MM +Amplifier_Operational:LMH6609MA +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 +Amplifier_Operational:LT1363 +Amplifier_Operational:LT1492 +Amplifier_Operational:LT1493 +Amplifier_Operational:LT6015xS5 +Amplifier_Operational:LT6230xS6 +Amplifier_Operational:LT6234 +Amplifier_Operational:LT6237 +Amplifier_Operational:LTC1151CN8 +Amplifier_Operational:LTC1151CSW +Amplifier_Operational:LTC1152 +Amplifier_Operational:LTC6081xDD +Amplifier_Operational:LTC6081xMS8 +Amplifier_Operational:LTC6082xDHC +Amplifier_Operational:LTC6082xGN +Amplifier_Operational:LTC6228xDC +Amplifier_Operational:LTC6228xS6 +Amplifier_Operational:LTC6228xS8 +Amplifier_Operational:LTC6229xDD +Amplifier_Operational:LTC6229xMS8E +Amplifier_Operational:LTC6253xMS8 +Amplifier_Operational:LTC6268xS6-10 +Amplifier_Operational:LTC6268xS8-10 +Amplifier_Operational:LTC6269xDD +Amplifier_Operational:LTC6269xMS8E +Amplifier_Operational:LTC6362xDD +Amplifier_Operational:LTC6362xMS8 +Amplifier_Operational:MAX4238ASA +Amplifier_Operational:MAX4238AUT +Amplifier_Operational:MAX4239ASA +Amplifier_Operational:MAX4239AUT +Amplifier_Operational:MAX4395ESD +Amplifier_Operational:MAX4395EUD +Amplifier_Operational:MC33078 +Amplifier_Operational:MC33079 +Amplifier_Operational:MC33172 +Amplifier_Operational:MC33174 +Amplifier_Operational:MC33178 +Amplifier_Operational:MC33179 +Amplifier_Operational:MCP6001-OT +Amplifier_Operational:MCP6001R +Amplifier_Operational:MCP6001U +Amplifier_Operational:MCP6001x-LT +Amplifier_Operational:MCP6002-xMC +Amplifier_Operational:MCP6002-xMS +Amplifier_Operational:MCP6002-xP +Amplifier_Operational:MCP6002-xSN +Amplifier_Operational:MCP6004 +Amplifier_Operational:MCP601-xOT +Amplifier_Operational:MCP601-xP +Amplifier_Operational:MCP601-xSN +Amplifier_Operational:MCP601-xST +Amplifier_Operational:MCP601R +Amplifier_Operational:MCP602 +Amplifier_Operational:MCP6022 +Amplifier_Operational:MCP603-xCH +Amplifier_Operational:MCP603-xP +Amplifier_Operational:MCP603-xSN +Amplifier_Operational:MCP603-xST +Amplifier_Operational:MCP604 +Amplifier_Operational:MCP6401RT-xOT +Amplifier_Operational:MCP6401T-xLT +Amplifier_Operational:MCP6401T-xOT +Amplifier_Operational:MCP6401UT-xOT +Amplifier_Operational:MCP6L01Rx-xOT +Amplifier_Operational:MCP6L01Ux-xOT +Amplifier_Operational:MCP6L01x-xLT +Amplifier_Operational:MCP6L01x-xOT +Amplifier_Operational:MCP6L02x-xMS +Amplifier_Operational:MCP6L02x-xSN +Amplifier_Operational:MCP6L04-xST +Amplifier_Operational:MCP6L04x-xSL +Amplifier_Operational:MCP6L91RT-EOT +Amplifier_Operational:MCP6L91T-EOT +Amplifier_Operational:MCP6L92 +Amplifier_Operational:MCP6L94 +Amplifier_Operational:MCP6V67EMS +Amplifier_Operational:MCP6V67xMNY +Amplifier_Operational:NCS20071SN +Amplifier_Operational:NCS20071XV +Amplifier_Operational:NCS20072D +Amplifier_Operational:NCS20072DM +Amplifier_Operational:NCS20072DTB +Amplifier_Operational:NCS20074D +Amplifier_Operational:NCS20074DTB +Amplifier_Operational:NCS2325D +Amplifier_Operational:NCS2325DM +Amplifier_Operational:NCS325 +Amplifier_Operational:NCS4325 +Amplifier_Operational:NE5532 +Amplifier_Operational:NE5534 +Amplifier_Operational:NJM2043 +Amplifier_Operational:NJM2114 +Amplifier_Operational:NJM4556A +Amplifier_Operational:NJM4558 +Amplifier_Operational:NJM4559 +Amplifier_Operational:NJM4560 +Amplifier_Operational:NJM4580 +Amplifier_Operational:NJM5532 +Amplifier_Operational:OP07 +Amplifier_Operational:OP1177AR +Amplifier_Operational:OP1177ARM +Amplifier_Operational:OP179GRT +Amplifier_Operational:OP179GS +Amplifier_Operational:OP249 +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 +Amplifier_Operational:OPA1692xDGK +Amplifier_Operational:OPA188xxD +Amplifier_Operational:OPA188xxDBV +Amplifier_Operational:OPA196xD +Amplifier_Operational:OPA196xDBV +Amplifier_Operational:OPA196xDGK +Amplifier_Operational:OPA197xD +Amplifier_Operational:OPA197xDBV +Amplifier_Operational:OPA197xDGK +Amplifier_Operational:OPA2134 +Amplifier_Operational:OPA2156xD +Amplifier_Operational:OPA2156xDGK +Amplifier_Operational:OPA2196xD +Amplifier_Operational:OPA2196xDGK +Amplifier_Operational:OPA2197xD +Amplifier_Operational:OPA2197xDGK +Amplifier_Operational:OPA2277 +Amplifier_Operational:OPA2325 +Amplifier_Operational:OPA2333xxD +Amplifier_Operational:OPA2333xxDGK +Amplifier_Operational:OPA2333xxDRB +Amplifier_Operational:OPA2340 +Amplifier_Operational:OPA2356xxD +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 +Amplifier_Operational:OPA330xxYFF +Amplifier_Operational:OPA333xxD +Amplifier_Operational:OPA333xxDBV +Amplifier_Operational:OPA333xxDCK +Amplifier_Operational:OPA336Nx +Amplifier_Operational:OPA336Ux +Amplifier_Operational:OPA340NA +Amplifier_Operational:OPA340P +Amplifier_Operational:OPA340UA +Amplifier_Operational:OPA355NA +Amplifier_Operational:OPA356xxD +Amplifier_Operational:OPA356xxDBV +Amplifier_Operational:OPA365xxD +Amplifier_Operational:OPA365xxDBV +Amplifier_Operational:OPA376xxD +Amplifier_Operational:OPA376xxDBV +Amplifier_Operational:OPA376xxDCK +Amplifier_Operational:OPA4134 +Amplifier_Operational:OPA4196xD +Amplifier_Operational:OPA4196xPW +Amplifier_Operational:OPA4197xD +Amplifier_Operational:OPA4197xPW +Amplifier_Operational:OPA4340EA +Amplifier_Operational:OPA4340UA +Amplifier_Operational:OPA4376 +Amplifier_Operational:OPA551P +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 +Amplifier_Operational:OPA843xD +Amplifier_Operational:OPA843xDBV +Amplifier_Operational:OPA846xD +Amplifier_Operational:OPA846xDBV +Amplifier_Operational:OPA847xD +Amplifier_Operational:OPA847xDBV +Amplifier_Operational:OPA855xDSG +Amplifier_Operational:OPA858xDSG +Amplifier_Operational:OPA859xDSG +Amplifier_Operational:OPA890xD +Amplifier_Operational:OPA890xDBV +Amplifier_Operational:RC4558 +Amplifier_Operational:RC4560 +Amplifier_Operational:RC4580 +Amplifier_Operational:SA5532 +Amplifier_Operational:SA5534 +Amplifier_Operational:THS3491xDDA +Amplifier_Operational:THS4631D +Amplifier_Operational:THS4631DDA +Amplifier_Operational:THS4631DGN +Amplifier_Operational:TL061 +Amplifier_Operational:TL062 +Amplifier_Operational:TL064 +Amplifier_Operational:TL071 +Amplifier_Operational:TL072 +Amplifier_Operational:TL074 +Amplifier_Operational:TL081 +Amplifier_Operational:TL082 +Amplifier_Operational:TL084 +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 +Amplifier_Operational:TLV2371P +Amplifier_Operational:TLV2372 +Amplifier_Operational:TLV6001DCK +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 +Amplifier_Operational:TLV9302xDDF +Amplifier_Operational:TLV9302xDGK +Amplifier_Operational:TLV9302xPW +Amplifier_Operational:TLV9304xD +Amplifier_Operational:TLV9304xPW +Amplifier_Operational:TS881xCx +Amplifier_Operational:TS881xLx +Amplifier_Operational:TS912 +Amplifier_Operational:TSV524xIQ4T +Amplifier_Operational:TSV911IDT +Amplifier_Operational:TSV911RILT +Amplifier_Operational:TSV911xxLx +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 +Amplifier_Video:THS7374 +Analog:AD5593R +Analog:AD630ARZ +Analog:AD637xQ +Analog:AD637xRZ +Analog:AD654JN +Analog:AD654JR +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 +Analog_ADC:AD7616 +Analog_ADC:AD7682BCP +Analog_ADC:AD7689xCP +Analog_ADC:AD7699BCP +Analog_ADC:AD7722 +Analog_ADC:AD7745 +Analog_ADC:AD7746 +Analog_ADC:AD7794 +Analog_ADC:AD7795 +Analog_ADC:AD7819 +Analog_ADC:AD7949BCP +Analog_ADC:AD9280ARS +Analog_ADC:AD9283 +Analog_ADC:ADC0800 +Analog_ADC:ADC08060 +Analog_ADC:ADC081C021CIMM +Analog_ADC:ADC0832 +Analog_ADC:ADC101C021CIMK +Analog_ADC:ADC101C021CIMM +Analog_ADC:ADC1173 +Analog_ADC:ADC121C021CIMM +Analog_ADC:ADC1283 +Analog_ADC:ADC128D818 +Analog_ADC:ADS1013IDGS +Analog_ADC:ADS1014IDGS +Analog_ADC:ADS1015IDGS +Analog_ADC:ADS1018IDGS +Analog_ADC:ADS1110 +Analog_ADC:ADS1113IDGS +Analog_ADC:ADS1114IDGS +Analog_ADC:ADS1115IDGS +Analog_ADC:ADS1118IDGS +Analog_ADC:ADS1120-PW +Analog_ADC:ADS1120-RVA +Analog_ADC:ADS1220xPW +Analog_ADC:ADS1232IPW +Analog_ADC:ADS1234IPW +Analog_ADC:ADS1243 +Analog_ADC:ADS1251 +Analog_ADC:ADS127L01IPBS +Analog_ADC:ADS1298xPAG +Analog_ADC:ADS7029 +Analog_ADC:ADS7039 +Analog_ADC:ADS7040xDCU +Analog_ADC:ADS7041xDCU +Analog_ADC:ADS7042xDCU +Analog_ADC:ADS7043xDCU +Analog_ADC:ADS7044xDCU +Analog_ADC:ADS7049 +Analog_ADC:ADS7828 +Analog_ADC:ADS7866 +Analog_ADC:ADS7867 +Analog_ADC:ADS7868 +Analog_ADC:ADS8681RUM +Analog_ADC:ADS8684 +Analog_ADC:ADS8685RUM +Analog_ADC:ADS8688 +Analog_ADC:ADS8689RUM +Analog_ADC:AMC3336 +Analog_ADC:CA3300 +Analog_ADC:HX711 +Analog_ADC:ICL7106CPL +Analog_ADC:ICL7107CPL +Analog_ADC:INA234AxYBJ +Analog_ADC:LTC1406CGN +Analog_ADC:LTC1406IGN +Analog_ADC:LTC1594CS +Analog_ADC:LTC1594IS +Analog_ADC:LTC1598CG +Analog_ADC:LTC1598IG +Analog_ADC:LTC1742 +Analog_ADC:LTC1744 +Analog_ADC:LTC1746 +Analog_ADC:LTC1748 +Analog_ADC:LTC1864 +Analog_ADC:LTC1864L +Analog_ADC:LTC1865-MS +Analog_ADC:LTC1865-S8 +Analog_ADC:LTC1865L-MS +Analog_ADC:LTC1865L-S8 +Analog_ADC:LTC2282xUP +Analog_ADC:LTC2284xUP +Analog_ADC:LTC2290xUP +Analog_ADC:LTC2291xUP +Analog_ADC:LTC2292xUP +Analog_ADC:LTC2293xUP +Analog_ADC:LTC2294xUP +Analog_ADC:LTC2295xUP +Analog_ADC:LTC2296xUP +Analog_ADC:LTC2297xUP +Analog_ADC:LTC2298xUP +Analog_ADC:LTC2299xUP +Analog_ADC:LTC2309xF +Analog_ADC:LTC2309xUF +Analog_ADC:LTC2311-16 +Analog_ADC:LTC2325-16 +Analog_ADC:LTC2358-16 +Analog_ADC:LTC2358-18 +Analog_ADC:LTC2451xDDB +Analog_ADC:LTC2451xTS8 +Analog_ADC:LTC2508CDKD-32 +Analog_ADC:LTC2508IDKD-32 +Analog_ADC:MAX1112 +Analog_ADC:MAX11120xTI +Analog_ADC:MAX11121xTI +Analog_ADC:MAX11122xTI +Analog_ADC:MAX11123xTI +Analog_ADC:MAX11124xTI +Analog_ADC:MAX11125xTI +Analog_ADC:MAX11126xTI +Analog_ADC:MAX11127xTI +Analog_ADC:MAX11128xTI +Analog_ADC:MAX1113 +Analog_ADC:MAX11612 +Analog_ADC:MAX11613 +Analog_ADC:MAX11614 +Analog_ADC:MAX11615 +Analog_ADC:MAX11616 +Analog_ADC:MAX11617 +Analog_ADC:MAX1248 +Analog_ADC:MAX1249 +Analog_ADC:MAX1274 +Analog_ADC:MAX1275 +Analog_ADC:MCP3002 +Analog_ADC:MCP3004 +Analog_ADC:MCP3008 +Analog_ADC:MCP3201 +Analog_ADC:MCP3202 +Analog_ADC:MCP3204 +Analog_ADC:MCP3208 +Analog_ADC:MCP3221 +Analog_ADC:MCP3301 +Analog_ADC:MCP3421A0T-ECH +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 +Analog_ADC:MCP3553-ESN +Analog_DAC:AD390JD +Analog_DAC:AD390KD +Analog_DAC:AD558JN +Analog_DAC:AD558JP +Analog_DAC:AD558KN +Analog_DAC:AD558KP +Analog_DAC:AD5687BCPZ +Analog_DAC:AD5687BRUZ +Analog_DAC:AD5687RBCPZ +Analog_DAC:AD5687RBRUZ +Analog_DAC:AD5689BCPZ +Analog_DAC:AD5689BRUZ +Analog_DAC:AD5689RxCPZ +Analog_DAC:AD5689RxRUZ +Analog_DAC:AD5691RxRM +Analog_DAC:AD5692RxRM +Analog_DAC:AD5693RxRM +Analog_DAC:AD5697RBCPZ +Analog_DAC:AD5697RBRUZ +Analog_DAC:AD5781xRUZ +Analog_DAC:AD5791xRUZ +Analog_DAC:AD7224KN +Analog_DAC:AD7224KP +Analog_DAC:AD7224KR-1 +Analog_DAC:AD7224KR-18 +Analog_DAC:AD7224LN +Analog_DAC:AD7224LP +Analog_DAC:AD7224LR-1 +Analog_DAC:AD7224LR-18 +Analog_DAC:AD7225BRS +Analog_DAC:AD7225CRS +Analog_DAC:AD7225KN +Analog_DAC:AD7225KP +Analog_DAC:AD7225KR +Analog_DAC:AD7225LN +Analog_DAC:AD7225LP +Analog_DAC:AD7225LR +Analog_DAC:AD7226BRSZ +Analog_DAC:AD7226KN +Analog_DAC:AD7226KP +Analog_DAC:AD7226KR +Analog_DAC:AD7228ABN +Analog_DAC:AD7228ABP +Analog_DAC:AD7228ABR +Analog_DAC:AD7228ACN +Analog_DAC:AD7228ACP +Analog_DAC:AD7228ACR +Analog_DAC:AD7304 +Analog_DAC:AD7305 +Analog_DAC:AD7390 +Analog_DAC:AD7391 +Analog_DAC:AD7533JN +Analog_DAC:AD7533JP +Analog_DAC:AD7533KN +Analog_DAC:AD7533KP +Analog_DAC:AD7533KR +Analog_DAC:AD7533LN +Analog_DAC:AD775 +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 +Analog_DAC:LTC1446L +Analog_DAC:LTC1664CGN +Analog_DAC:LTC1664CN +Analog_DAC:LTC1664IGN +Analog_DAC:LTC1664IN +Analog_DAC:MAX5138 +Analog_DAC:MAX5139 +Analog_DAC:MAX5215 +Analog_DAC:MAX5217 +Analog_DAC:MAX5717xSD +Analog_DAC:MAX5719xSD +Analog_DAC:MAX5741 +Analog_DAC:MAX5813 +Analog_DAC:MAX5813WLP +Analog_DAC:MAX5814 +Analog_DAC:MAX5814WLP +Analog_DAC:MAX5815 +Analog_DAC:MAX5815WLP +Analog_DAC:MC1408_DIP +Analog_DAC:MC1408_SOIC +Analog_DAC:MCP4725xxx-xCH +Analog_DAC:MCP4728 +Analog_DAC:MCP4801 +Analog_DAC:MCP4801-EMC +Analog_DAC:MCP4802 +Analog_DAC:MCP4811 +Analog_DAC:MCP4811-EMC +Analog_DAC:MCP4812 +Analog_DAC:MCP4821 +Analog_DAC:MCP4821-EMC +Analog_DAC:MCP4822 +Analog_DAC:MCP4901 +Analog_DAC:MCP4901-EMC +Analog_DAC:MCP4902 +Analog_DAC:MCP4911 +Analog_DAC:MCP4911-EMC +Analog_DAC:MCP4912 +Analog_DAC:MCP4921 +Analog_DAC:MCP4921-EMC +Analog_DAC:MCP4921-EMS +Analog_DAC:MCP4921-EP +Analog_DAC:MCP4921-ESN +Analog_DAC:MCP4922 +Analog_DAC:MCP4922-EP +Analog_DAC:MCP4922-ESL +Analog_DAC:MCP4922-EST +Analog_DAC:THS5641AxDW +Analog_DAC:THS5641AxPW +Analog_DAC:TLV5627CD +Analog_DAC:TLV5627CPW +Analog_Switch:ADG1207BCPZ +Analog_Switch:ADG1408YRUZ +Analog_Switch:ADG1414BRU +Analog_Switch:ADG1607xCP +Analog_Switch:ADG417BN +Analog_Switch:ADG417BR +Analog_Switch:ADG419BN +Analog_Switch:ADG419BR +Analog_Switch:ADG419BRM +Analog_Switch:ADG633YCP +Analog_Switch:ADG633YRU +Analog_Switch:ADG658YCP +Analog_Switch:ADG707BRU +Analog_Switch:ADG715 +Analog_Switch:ADG728 +Analog_Switch:ADG729 +Analog_Switch:ADG733BRQ +Analog_Switch:ADG733BRU +Analog_Switch:ADG734 +Analog_Switch:ADG758CPZ +Analog_Switch:ADG824BCP +Analog_Switch:ADG884xCP +Analog_Switch:ADG884xRM +Analog_Switch:CBTL02043A +Analog_Switch:CBTL02043B +Analog_Switch:CD4051B +Analog_Switch:CD4052B +Analog_Switch:CD4053B +Analog_Switch:CD4066BE +Analog_Switch:CD4066BM +Analog_Switch:CD4066BNS +Analog_Switch:CD4066BPW +Analog_Switch:CD4097B +Analog_Switch:DG308AxJ +Analog_Switch:DG308AxY +Analog_Switch:DG309xJ +Analog_Switch:DG309xY +Analog_Switch:DG411xJ +Analog_Switch:DG411xUE +Analog_Switch:DG411xY +Analog_Switch:DG412xJ +Analog_Switch:DG412xUE +Analog_Switch:DG412xY +Analog_Switch:DG413xJ +Analog_Switch:DG413xUE +Analog_Switch:DG413xY +Analog_Switch:DG417LDJ +Analog_Switch:DG417LDJ_Maxim +Analog_Switch:DG417LDY +Analog_Switch:DG417LDY_Maxim +Analog_Switch:DG417LEUA +Analog_Switch:DG417LEUA_Maxim +Analog_Switch:DG417xJ +Analog_Switch:DG417xY +Analog_Switch:DG418LDJ +Analog_Switch:DG418LDJ_Maxim +Analog_Switch:DG418LDY +Analog_Switch:DG418LDY_Maxim +Analog_Switch:DG418LEUA +Analog_Switch:DG418LEUA_Maxim +Analog_Switch:DG418xJ +Analog_Switch:DG418xY +Analog_Switch:DG419LDJ +Analog_Switch:DG419LDJ_Maxim +Analog_Switch:DG419LDY +Analog_Switch:DG419LDY_Maxim +Analog_Switch:DG419LEUA +Analog_Switch:DG419LEUA_Maxim +Analog_Switch:DG419xJ +Analog_Switch:DG419xY +Analog_Switch:DG441xJ +Analog_Switch:DG441xY +Analog_Switch:DG442xJ +Analog_Switch:DG442xY +Analog_Switch:DG884DN +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 +Analog_Switch:MAX312CPE +Analog_Switch:MAX312CSE +Analog_Switch:MAX312CUE +Analog_Switch:MAX313CPE +Analog_Switch:MAX313CSE +Analog_Switch:MAX313CUE +Analog_Switch:MAX314CPE +Analog_Switch:MAX314CSE +Analog_Switch:MAX314CUE +Analog_Switch:MAX317xPA +Analog_Switch:MAX317xSA +Analog_Switch:MAX318xPA +Analog_Switch:MAX318xSA +Analog_Switch:MAX319xPA +Analog_Switch:MAX319xSA +Analog_Switch:MAX323CPA +Analog_Switch:MAX323CSA +Analog_Switch:MAX323CUA +Analog_Switch:MAX324CPA +Analog_Switch:MAX324CSA +Analog_Switch:MAX324CUA +Analog_Switch:MAX325CPA +Analog_Switch:MAX325CSA +Analog_Switch:MAX325CUA +Analog_Switch:MAX333 +Analog_Switch:MAX333A +Analog_Switch:MAX394 +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: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:TS5A3159ADCK +Analog_Switch:TS5A3159AYZPR +Analog_Switch:TS5A3159DBV +Analog_Switch:TS5A3159DCK +Analog_Switch:TS5A3160DBV +Analog_Switch:TS5A3160DCK +Analog_Switch:TS5A3166DBVR +Analog_Switch:TS5A3166DCKR +Analog_Switch:TS5A63157DBV +Audio:AD1853 +Audio:AD1855 +Audio:AD1955 +Audio:ADAU1978xBCP +Audio:ADAU1979xBCP +Audio:AK5392VS +Audio:AK5393VS +Audio:AK5394AVS +Audio:AK5720VT +Audio:AK7742EQ +Audio:AS3310 +Audio:AS3320 +Audio:AS3320F +Audio:AS3330 +Audio:AS3330F +Audio:AS3340 +Audio:AS3345 +Audio:AS3345F +Audio:AS3360 +Audio:CS4245 +Audio:CS4265 +Audio:CS4270 +Audio:CS4272 +Audio:CS4334 +Audio:CS4344 +Audio:CS4345 +Audio:CS4348 +Audio:CS43L21 +Audio:CS5343 +Audio:CS5344 +Audio:CS5361 +Audio:CS8406 +Audio:CS8414 +Audio:CS8416-xNZ +Audio:CS8416-xSZ +Audio:CS8416-xZZ +Audio:CS8420 +Audio:DSD1794A +Audio:ISD25120P +Audio:ISD25120S +Audio:ISD2560E +Audio:ISD2560P +Audio:ISD2560S +Audio:ISD2575E +Audio:ISD2575P +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 +Audio:PCM5101A +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 +Audio:TDA1022 +Audio:THAT1580 +Audio:THAT1583 +Audio:THAT5171 +Audio:THAT5173 +Audio:THAT5263 +Audio:THAT6261 +Audio:THAT6262 +Audio:THAT6263 +Audio:TLV320AIC23BPW +Audio:TLV320AIC23BRHD +Audio:TLV320AIC23BxQE +Audio:TLV320AIC3100 +Audio:TPA5050 +Audio:UDA1334ATS +Audio:WM8731CLSEFL +Audio:WM8731CSEFL +Audio:WM8731SEDS +Audio:YM2149 +Battery_Management:ADP5063 +Battery_Management:ADP5090ACP +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 +Battery_Management:BQ24012 +Battery_Management:BQ24013 +Battery_Management:BQ24072RGT +Battery_Management:BQ24073RGT +Battery_Management:BQ24074RGT +Battery_Management:BQ24075RGT +Battery_Management:BQ24079RGT +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 +Battery_Management:BQ27441DRZR-G1A +Battery_Management:BQ27441DRZR-G1B +Battery_Management:BQ27441DRZT-G1A +Battery_Management:BQ27441DRZT-G1B +Battery_Management:BQ27750 +Battery_Management:BQ297xy +Battery_Management:BQ51050BRHL +Battery_Management:BQ51050BYFP +Battery_Management:BQ51051BRHL +Battery_Management:BQ51051BYFP +Battery_Management:BQ51052BYFP +Battery_Management:BQ76200PW +Battery_Management:BQ76920PW +Battery_Management:BQ76930DBT +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 +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 +Battery_Management:LTC3555-3 +Battery_Management:LTC4001 +Battery_Management:LTC4001-1 +Battery_Management:LTC4002EDD-4.2 +Battery_Management:LTC4002EDD-8.4 +Battery_Management:LTC4002ES8-4.2 +Battery_Management:LTC4002ES8-8.4 +Battery_Management:LTC4007 +Battery_Management:LTC4011CFE +Battery_Management:LTC4054ES5-4.2 +Battery_Management:LTC4054XES5-4.2 +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 +Battery_Management:LTC6804-1 +Battery_Management:MAX1647 +Battery_Management:MAX1648 +Battery_Management:MAX17261xxTD +Battery_Management:MAX17261xxWL +Battery_Management:MAX17263xxTE +Battery_Management:MAX1811 +Battery_Management:MAX1873REEE +Battery_Management:MAX1873SEEE +Battery_Management:MAX1873TEEE +Battery_Management:MAX712CPE +Battery_Management:MAX712CSE +Battery_Management:MAX712EPE +Battery_Management:MAX712ESE +Battery_Management:MAX712MJE +Battery_Management:MAX713CPE +Battery_Management:MAX713CSE +Battery_Management:MAX713EPE +Battery_Management:MAX713ESE +Battery_Management:MAX713MJE +Battery_Management:MC34673 +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 +Battery_Management:MCP73871 +Battery_Management:MCP73871-1AA +Battery_Management:MCP73871-1CA +Battery_Management:MCP73871-1CC +Battery_Management:MCP73871-2AA +Battery_Management:MCP73871-2CA +Battery_Management:MCP73871-2CC +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 +Comparator:ADCMP354 +Comparator:ADCMP356 +Comparator:LM2901 +Comparator:LM2903 +Comparator:LM311 +Comparator:LM319 +Comparator:LM319H +Comparator:LM339 +Comparator:LM393 +Comparator:LM397 +Comparator:LMH7324 +Comparator:LMV331 +Comparator:LMV339 +Comparator:LMV393 +Comparator:LMV7219M5 +Comparator:LMV7219M7 +Comparator:LMV7271 +Comparator:LMV7272 +Comparator:LMV7275 +Comparator:LP2901D +Comparator:LT1011 +Comparator:LT1016 +Comparator:LT1116 +Comparator:LT1711xMS8 +Comparator:LTC6752xMS8-2 +Comparator:LTC6752xS5 +Comparator:LTC6752xSC6-1 +Comparator:LTC6752xSC6-4 +Comparator:LTC6752xUD-3 +Comparator:LTC6754xSC6 +Comparator:LTC6754xUD +Comparator:MAX9031AU +Comparator:MAX9031AX +Comparator:MAX941xPA +Comparator:MAX941xSA +Comparator:MAX941xUA +Comparator:MCP6561-OT +Comparator:MCP6561R +Comparator:MCP6561U +Comparator:MCP6561x-LT +Comparator:MCP6562 +Comparator:MCP6566 +Comparator:MCP6566R +Comparator:MCP6566U +Comparator:MCP6567 +Comparator:MCP6569 +Comparator:MCP65R41 +Comparator:MCP65R46 +Comparator:MIC845H +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 +Connector:4P4C_Shielded +Connector:6P2C +Connector:6P2C_Shielded +Connector:6P4C +Connector:6P4C_Shielded +Connector:6P6C +Connector:6P6C_Shielded +Connector:8P4C +Connector:8P4C_Shielded +Connector:8P8C +Connector:8P8C_LED +Connector:8P8C_LED_Shielded +Connector:8P8C_LED_Shielded_x2 +Connector:8P8C_Shielded +Connector:ATX-20 +Connector:ATX-24 +Connector:AVR-ISP-10 +Connector:AVR-ISP-6 +Connector:AVR-JTAG-10 +Connector:AVR-PDI-6 +Connector:AVR-TPI-6 +Connector:AVR-UPDI-6 +Connector:Barrel_Jack +Connector:Barrel_Jack_MountingPin +Connector:Barrel_Jack_Switch +Connector:Barrel_Jack_Switch_MountingPin +Connector:Barrel_Jack_Switch_Pin3Ring +Connector:Bus_ISA_16bit +Connector:Bus_ISA_8bit +Connector:Bus_M.2_Socket_A +Connector:Bus_M.2_Socket_B +Connector:Bus_M.2_Socket_E +Connector:Bus_M.2_Socket_M +Connector:Bus_PCI_32bit_5V +Connector:Bus_PCI_32bit_Universal +Connector:Bus_PCI_Express_Mini +Connector:Bus_PCI_Express_x1 +Connector:Bus_PCI_Express_x16 +Connector:Bus_PCI_Express_x4 +Connector:Bus_PCI_Express_x8 +Connector:CUI_PD-30 +Connector:CUI_PD-30S +Connector:CoaxialSwitch_Testpoint +Connector:Conn_01x01_Pin +Connector:Conn_01x01_Socket +Connector:Conn_01x02_Pin +Connector:Conn_01x02_Socket +Connector:Conn_01x03_Pin +Connector:Conn_01x03_Socket +Connector:Conn_01x04_Pin +Connector:Conn_01x04_Socket +Connector:Conn_01x05_Pin +Connector:Conn_01x05_Socket +Connector:Conn_01x06_Pin +Connector:Conn_01x06_Socket +Connector:Conn_01x07_Pin +Connector:Conn_01x07_Socket +Connector:Conn_01x08_Pin +Connector:Conn_01x08_Socket +Connector:Conn_01x09_Pin +Connector:Conn_01x09_Socket +Connector:Conn_01x10_Pin +Connector:Conn_01x10_Socket +Connector:Conn_01x11_Pin +Connector:Conn_01x11_Socket +Connector:Conn_01x12_Pin +Connector:Conn_01x12_Socket +Connector:Conn_01x13_Pin +Connector:Conn_01x13_Socket +Connector:Conn_01x14_Pin +Connector:Conn_01x14_Socket +Connector:Conn_01x15_Pin +Connector:Conn_01x15_Socket +Connector:Conn_01x16_Pin +Connector:Conn_01x16_Socket +Connector:Conn_01x17_Pin +Connector:Conn_01x17_Socket +Connector:Conn_01x18_Pin +Connector:Conn_01x18_Socket +Connector:Conn_01x19_Pin +Connector:Conn_01x19_Socket +Connector:Conn_01x20_Pin +Connector:Conn_01x20_Socket +Connector:Conn_01x21_Pin +Connector:Conn_01x21_Socket +Connector:Conn_01x22_Pin +Connector:Conn_01x22_Socket +Connector:Conn_01x23_Pin +Connector:Conn_01x23_Socket +Connector:Conn_01x24_Pin +Connector:Conn_01x24_Socket +Connector:Conn_01x25_Pin +Connector:Conn_01x25_Socket +Connector:Conn_01x26_Pin +Connector:Conn_01x26_Socket +Connector:Conn_01x27_Pin +Connector:Conn_01x27_Socket +Connector:Conn_01x28_Pin +Connector:Conn_01x28_Socket +Connector:Conn_01x29_Pin +Connector:Conn_01x29_Socket +Connector:Conn_01x30_Pin +Connector:Conn_01x30_Socket +Connector:Conn_01x31_Pin +Connector:Conn_01x31_Socket +Connector:Conn_01x32_Pin +Connector:Conn_01x32_Socket +Connector:Conn_01x33_Pin +Connector:Conn_01x33_Socket +Connector:Conn_01x34_Pin +Connector:Conn_01x34_Socket +Connector:Conn_01x35_Pin +Connector:Conn_01x35_Socket +Connector:Conn_01x36_Pin +Connector:Conn_01x36_Socket +Connector:Conn_01x37_Pin +Connector:Conn_01x37_Socket +Connector:Conn_01x38_Pin +Connector:Conn_01x38_Socket +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_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 +Connector:DIN-5_180degree +Connector:DIN-6 +Connector:DIN-7 +Connector:DIN-7_CenterPin7 +Connector:DIN-8 +Connector:DIN41612_01x32_A +Connector:DIN41612_02x05_AB_EvenPins +Connector:DIN41612_02x05_AC_EvenPins +Connector:DIN41612_02x05_AE_EvenPins +Connector:DIN41612_02x05_ZB_EvenPins +Connector:DIN41612_02x08_AB_EvenPins +Connector:DIN41612_02x08_AC_EvenPins +Connector:DIN41612_02x08_AE_EvenPins +Connector:DIN41612_02x08_ZB_EvenPins +Connector:DIN41612_02x10_AB +Connector:DIN41612_02x10_AC +Connector:DIN41612_02x10_AE +Connector:DIN41612_02x10_ZB +Connector:DIN41612_02x16_AB +Connector:DIN41612_02x16_AB_EvenPins +Connector:DIN41612_02x16_AC +Connector:DIN41612_02x16_AC_EvenPins +Connector:DIN41612_02x16_AE +Connector:DIN41612_02x16_AE_EvenPins +Connector:DIN41612_02x16_ZB +Connector:DIN41612_02x16_ZB_EvenPins +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 +Connector:HDMI_B +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 +Connector:LEMO2 +Connector:LEMO4 +Connector:LEMO5 +Connector:LEMO6 +Connector:MXM3.0 +Connector:Micro_SD_Card +Connector:Micro_SD_Card_Det1 +Connector:Micro_SD_Card_Det2 +Connector:Micro_SD_Card_Det_Hirose_DM3AT +Connector:Microsemi_FlashPro-JTAG-10 +Connector:Mini-DIN-3 +Connector:Mini-DIN-4 +Connector:Mini-DIN-5 +Connector:Mini-DIN-6 +Connector:Mini-DIN-7 +Connector:Mini-DIN-8 +Connector:RJ10 +Connector:RJ10_Shielded +Connector:RJ11 +Connector:RJ11_Shielded +Connector:RJ12 +Connector:RJ12_Shielded +Connector:RJ13 +Connector:RJ13_Shielded +Connector:RJ14 +Connector:RJ14_Shielded +Connector:RJ18 +Connector:RJ18_Shielded +Connector:RJ22 +Connector:RJ22_Shielded +Connector:RJ25 +Connector:RJ25_Shielded +Connector:RJ31 +Connector:RJ31_Shielded +Connector:RJ32 +Connector:RJ32_Shielded +Connector:RJ33 +Connector:RJ33_Shielded +Connector:RJ34 +Connector:RJ34_Shielded +Connector:RJ35 +Connector:RJ35_Shielded +Connector:RJ38 +Connector:RJ38_Shielded +Connector:RJ41 +Connector:RJ41_Shielded +Connector:RJ45 +Connector:RJ45_Abracon_ARJP11A-MASA-B-A-EMU2 +Connector:RJ45_Amphenol_RJMG1BD3B8K1ANR +Connector:RJ45_Bel_SI-60062-F +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 +Connector:RJ45_Pulse_JXD6-0001NL +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 +Connector:RJ49 +Connector:RJ49_Shielded +Connector:RJ61 +Connector:RJ61_Shielded +Connector:RJ9 +Connector:RJ9_Shielded +Connector:Raspberry_Pi_4 +Connector:SCART-F +Connector:SD_Card_Device +Connector:SD_Card_Receptacle +Connector:SIM_Card +Connector:SIM_Card_Shielded +Connector:SODIMM-200 +Connector:SODIMM-200_Split +Connector:Samtec_ASP-134486-01 +Connector:Samtec_ASP-134602-01 +Connector:Screw_Terminal_01x01 +Connector:Screw_Terminal_01x02 +Connector:Screw_Terminal_01x03 +Connector:Screw_Terminal_01x04 +Connector:Screw_Terminal_01x05 +Connector:Screw_Terminal_01x06 +Connector:Screw_Terminal_01x07 +Connector:Screw_Terminal_01x08 +Connector:Screw_Terminal_01x09 +Connector:Screw_Terminal_01x10 +Connector:Screw_Terminal_01x11 +Connector:Screw_Terminal_01x12 +Connector:Screw_Terminal_01x13 +Connector:Screw_Terminal_01x14 +Connector:Screw_Terminal_01x15 +Connector:Screw_Terminal_01x16 +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 +Connector:TestPoint_Flag +Connector:TestPoint_Probe +Connector:TestPoint_Small +Connector:UEXT_Host +Connector:UEXT_Slave +Connector:USB3_A +Connector:USB3_A_Stacked +Connector:USB3_B +Connector:USB3_B_Micro +Connector:USB_A +Connector:USB_A_Stacked +Connector:USB_B +Connector:USB_B_Micro +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_14P +Connector:USB_C_Receptacle_USB2.0_16P +Connector:USB_OTG +Connector_Audio:AudioJack2 +Connector_Audio:AudioJack2_Dual_Ground_Switch +Connector_Audio:AudioJack2_Dual_Switch +Connector_Audio:AudioJack2_Ground +Connector_Audio:AudioJack2_Ground_Switch +Connector_Audio:AudioJack2_Ground_SwitchT +Connector_Audio:AudioJack2_Switch +Connector_Audio:AudioJack2_SwitchT +Connector_Audio:AudioJack3 +Connector_Audio:AudioJack3_Dual_Ground_Switch +Connector_Audio:AudioJack3_Dual_Switch +Connector_Audio:AudioJack3_Ground +Connector_Audio:AudioJack3_Ground_Switch +Connector_Audio:AudioJack3_Ground_SwitchTR +Connector_Audio:AudioJack3_Switch +Connector_Audio:AudioJack3_SwitchT +Connector_Audio:AudioJack3_SwitchTR +Connector_Audio:AudioJack4 +Connector_Audio:AudioJack4_Ground +Connector_Audio:AudioJack4_Ground_SwitchTR1 +Connector_Audio:AudioJack4_SwitchT1 +Connector_Audio:AudioJack4_SwitchTR1 +Connector_Audio:AudioJack5 +Connector_Audio:AudioJack5_Ground +Connector_Audio:AudioPlug2 +Connector_Audio:AudioPlug3 +Connector_Audio:AudioPlug4 +Connector_Audio:NC3FAAH +Connector_Audio:NC3FAAH-0 +Connector_Audio:NC3FAAH1 +Connector_Audio:NC3FAAH1-0 +Connector_Audio:NC3FAAH1-DA +Connector_Audio:NC3FAAH2 +Connector_Audio:NC3FAAH2-0 +Connector_Audio:NC3FAAV +Connector_Audio:NC3FAAV-0 +Connector_Audio:NC3FAAV1 +Connector_Audio:NC3FAAV1-0 +Connector_Audio:NC3FAAV1-DA +Connector_Audio:NC3FAAV2 +Connector_Audio:NC3FAAV2-0 +Connector_Audio:NC3FAH +Connector_Audio:NC3FAH-0 +Connector_Audio:NC3FAH1 +Connector_Audio:NC3FAH1-0 +Connector_Audio:NC3FAH1-DA +Connector_Audio:NC3FAH2 +Connector_Audio:NC3FAH2-0 +Connector_Audio:NC3FAH2-DA +Connector_Audio:NC3FAHL-0 +Connector_Audio:NC3FAHL1 +Connector_Audio:NC3FAHL1-0 +Connector_Audio:NC3FAHR-0 +Connector_Audio:NC3FAHR1 +Connector_Audio:NC3FAHR1-0 +Connector_Audio:NC3FAHR2 +Connector_Audio:NC3FAHR2-0 +Connector_Audio:NC3FAV +Connector_Audio:NC3FAV-0 +Connector_Audio:NC3FAV1 +Connector_Audio:NC3FAV1-0 +Connector_Audio:NC3FAV1-DA +Connector_Audio:NC3FAV2 +Connector_Audio:NC3FAV2-0 +Connector_Audio:NC3FAV2-DA +Connector_Audio:NC3FBH1 +Connector_Audio:NC3FBH1-B +Connector_Audio:NC3FBH1-DA +Connector_Audio:NC3FBH1-E +Connector_Audio:NC3FBH2 +Connector_Audio:NC3FBH2-B +Connector_Audio:NC3FBH2-DA +Connector_Audio:NC3FBH2-E +Connector_Audio:NC3FBHL1 +Connector_Audio:NC3FBV1 +Connector_Audio:NC3FBV1-0 +Connector_Audio:NC3FBV1-B +Connector_Audio:NC3FBV1-DA +Connector_Audio:NC3FBV2 +Connector_Audio:NC3FBV2-B +Connector_Audio:NC3FBV2-DA +Connector_Audio:NC3FBV2-SW +Connector_Audio:NC3MAAH +Connector_Audio:NC3MAAH-0 +Connector_Audio:NC3MAAH-1 +Connector_Audio:NC3MAAV +Connector_Audio:NC3MAAV-0 +Connector_Audio:NC3MAAV-1 +Connector_Audio:NC3MAFH-PH +Connector_Audio:NC3MAH +Connector_Audio:NC3MAH-0 +Connector_Audio:NC3MAHL +Connector_Audio:NC3MAHR +Connector_Audio:NC3MAMH-PH +Connector_Audio:NC3MAV +Connector_Audio:NC3MAV-0 +Connector_Audio:NC3MBH +Connector_Audio:NC3MBH-0 +Connector_Audio:NC3MBH-1 +Connector_Audio:NC3MBH-B +Connector_Audio:NC3MBH-E +Connector_Audio:NC3MBHL +Connector_Audio:NC3MBHL-B +Connector_Audio:NC3MBHR +Connector_Audio:NC3MBHR-B +Connector_Audio:NC3MBV +Connector_Audio:NC3MBV-0 +Connector_Audio:NC3MBV-1 +Connector_Audio:NC3MBV-B +Connector_Audio:NC3MBV-E +Connector_Audio:NC3MBV-SW +Connector_Audio:NC4FAH +Connector_Audio:NC4FAH-0 +Connector_Audio:NC4FAV +Connector_Audio:NC4FAV-0 +Connector_Audio:NC4FBH +Connector_Audio:NC4FBV +Connector_Audio:NC4MAH +Connector_Audio:NC4MAV +Connector_Audio:NC4MBH +Connector_Audio:NC4MBV +Connector_Audio:NC5FAH +Connector_Audio:NC5FAH-0 +Connector_Audio:NC5FAH-DA +Connector_Audio:NC5FAV +Connector_Audio:NC5FAV-DA +Connector_Audio:NC5FAV-SW +Connector_Audio:NC5FBH +Connector_Audio:NC5FBH-B +Connector_Audio:NC5FBV +Connector_Audio:NC5FBV-B +Connector_Audio:NC5FBV-SW +Connector_Audio:NC5MAH +Connector_Audio:NC5MAV +Connector_Audio:NC5MAV-SW +Connector_Audio:NC5MBH +Connector_Audio:NC5MBH-B +Connector_Audio:NC5MBV +Connector_Audio:NC5MBV-B +Connector_Audio:NC5MBV-SW +Connector_Audio:NCJ10FI-H +Connector_Audio:NCJ10FI-H-0 +Connector_Audio:NCJ10FI-V +Connector_Audio:NCJ10FI-V-0 +Connector_Audio:NCJ5FI-H +Connector_Audio:NCJ5FI-H-0 +Connector_Audio:NCJ5FI-V +Connector_Audio:NCJ5FI-V-0 +Connector_Audio:NCJ6FA-H +Connector_Audio:NCJ6FA-H-0 +Connector_Audio:NCJ6FA-H-DA +Connector_Audio:NCJ6FA-V +Connector_Audio:NCJ6FA-V-0 +Connector_Audio:NCJ6FA-V-DA +Connector_Audio:NCJ6FI-H +Connector_Audio:NCJ6FI-H-0 +Connector_Audio:NCJ6FI-V +Connector_Audio:NCJ6FI-V-0 +Connector_Audio:NCJ9FI-H +Connector_Audio:NCJ9FI-H-0 +Connector_Audio:NCJ9FI-V +Connector_Audio:NCJ9FI-V-0 +Connector_Audio:NJ2FD-V +Connector_Audio:NJ3FD-V +Connector_Audio:NJ5FD-V +Connector_Audio:NJ6FD-V +Connector_Audio:NJ6TB-V +Connector_Audio:NL2MDXX-H-3 +Connector_Audio:NL2MDXX-V +Connector_Audio:NL4MDXX-H-2 +Connector_Audio:NL4MDXX-H-3 +Connector_Audio:NL4MDXX-V +Connector_Audio:NL4MDXX-V-2 +Connector_Audio:NL4MDXX-V-3 +Connector_Audio:NL8MDXX-V +Connector_Audio:NL8MDXX-V-3 +Connector_Audio:NLJ2MDXX-H +Connector_Audio:NLJ2MDXX-V +Connector_Audio:NLT4MD-V +Connector_Audio:NMJ4HCD2 +Connector_Audio:NMJ4HFD2 +Connector_Audio:NMJ4HFD3 +Connector_Audio:NMJ4HHD2 +Connector_Audio:NMJ6HCD2 +Connector_Audio:NMJ6HCD3 +Connector_Audio:NMJ6HFD2 +Connector_Audio:NMJ6HFD2-AU +Connector_Audio:NMJ6HFD3 +Connector_Audio:NMJ6HFD4 +Connector_Audio:NMJ6HHD2 +Connector_Audio:NRJ3HF-1 +Connector_Audio:NRJ4HF +Connector_Audio:NRJ4HF-1 +Connector_Audio:NRJ4HH +Connector_Audio:NRJ4HH-1 +Connector_Audio:NRJ6HF +Connector_Audio:NRJ6HF-1 +Connector_Audio:NRJ6HF-1-AU +Connector_Audio:NRJ6HF-AU +Connector_Audio:NRJ6HH +Connector_Audio:NRJ6HH-1 +Connector_Audio:NRJ6HH-AU +Connector_Audio:NRJ6HM-1 +Connector_Audio:NRJ6HM-1-AU +Connector_Audio:NRJ6HM-1-PRE +Connector_Audio:NSJ12HC +Connector_Audio:NSJ12HF-1 +Connector_Audio:NSJ12HH-1 +Connector_Audio:NSJ12HL +Connector_Audio:NSJ8HC +Connector_Audio:NSJ8HL +Connector_Audio:SpeakON_NL2 +Connector_Audio:SpeakON_NL2_AudioJack2_Combo +Connector_Audio:SpeakON_NL4 +Connector_Audio:SpeakON_NL4_Switch +Connector_Audio:SpeakON_NL8 +Connector_Audio:XLR3 +Connector_Audio:XLR3_AudioJack2_Combo +Connector_Audio:XLR3_AudioJack2_Combo_Ground +Connector_Audio:XLR3_AudioJack3_Combo +Connector_Audio:XLR3_AudioJack3_Combo_Ground +Connector_Audio:XLR3_AudioJack3_Combo_GroundSwitch_Switch +Connector_Audio:XLR3_AudioJack3_Combo_Ground_Switch +Connector_Audio:XLR3_AudioJack3_Combo_Switch +Connector_Audio:XLR3_Ground +Connector_Audio:XLR3_Ground_Switched +Connector_Audio:XLR3_Switched +Connector_Audio:XLR4 +Connector_Audio:XLR4_Ground +Connector_Audio:XLR5 +Connector_Audio:XLR5_Ground +Connector_Audio:XLR5_Ground_Switched +Connector_Audio:XLR6 +Connector_Generic:Conn_01x01 +Connector_Generic:Conn_01x02 +Connector_Generic:Conn_01x03 +Connector_Generic:Conn_01x04 +Connector_Generic:Conn_01x05 +Connector_Generic:Conn_01x06 +Connector_Generic:Conn_01x07 +Connector_Generic:Conn_01x08 +Connector_Generic:Conn_01x09 +Connector_Generic:Conn_01x10 +Connector_Generic:Conn_01x11 +Connector_Generic:Conn_01x12 +Connector_Generic:Conn_01x13 +Connector_Generic:Conn_01x14 +Connector_Generic:Conn_01x15 +Connector_Generic:Conn_01x16 +Connector_Generic:Conn_01x17 +Connector_Generic:Conn_01x18 +Connector_Generic:Conn_01x19 +Connector_Generic:Conn_01x20 +Connector_Generic:Conn_01x21 +Connector_Generic:Conn_01x22 +Connector_Generic:Conn_01x23 +Connector_Generic:Conn_01x24 +Connector_Generic:Conn_01x25 +Connector_Generic:Conn_01x26 +Connector_Generic:Conn_01x27 +Connector_Generic:Conn_01x28 +Connector_Generic:Conn_01x29 +Connector_Generic:Conn_01x30 +Connector_Generic:Conn_01x31 +Connector_Generic:Conn_01x32 +Connector_Generic:Conn_01x33 +Connector_Generic:Conn_01x34 +Connector_Generic:Conn_01x35 +Connector_Generic:Conn_01x36 +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 +Connector_Generic:Conn_02x02_Counter_Clockwise +Connector_Generic:Conn_02x02_Odd_Even +Connector_Generic:Conn_02x02_Row_Letter_First +Connector_Generic:Conn_02x02_Row_Letter_Last +Connector_Generic:Conn_02x02_Top_Bottom +Connector_Generic:Conn_02x03_Counter_Clockwise +Connector_Generic:Conn_02x03_Odd_Even +Connector_Generic:Conn_02x03_Row_Letter_First +Connector_Generic:Conn_02x03_Row_Letter_Last +Connector_Generic:Conn_02x03_Top_Bottom +Connector_Generic:Conn_02x04_Counter_Clockwise +Connector_Generic:Conn_02x04_Odd_Even +Connector_Generic:Conn_02x04_Row_Letter_First +Connector_Generic:Conn_02x04_Row_Letter_Last +Connector_Generic:Conn_02x04_Top_Bottom +Connector_Generic:Conn_02x05_Counter_Clockwise +Connector_Generic:Conn_02x05_Odd_Even +Connector_Generic:Conn_02x05_Row_Letter_First +Connector_Generic:Conn_02x05_Row_Letter_Last +Connector_Generic:Conn_02x05_Top_Bottom +Connector_Generic:Conn_02x06_Counter_Clockwise +Connector_Generic:Conn_02x06_Odd_Even +Connector_Generic:Conn_02x06_Row_Letter_First +Connector_Generic:Conn_02x06_Row_Letter_Last +Connector_Generic:Conn_02x06_Top_Bottom +Connector_Generic:Conn_02x07_Counter_Clockwise +Connector_Generic:Conn_02x07_Odd_Even +Connector_Generic:Conn_02x07_Row_Letter_First +Connector_Generic:Conn_02x07_Row_Letter_Last +Connector_Generic:Conn_02x07_Top_Bottom +Connector_Generic:Conn_02x08_Counter_Clockwise +Connector_Generic:Conn_02x08_Odd_Even +Connector_Generic:Conn_02x08_Row_Letter_First +Connector_Generic:Conn_02x08_Row_Letter_Last +Connector_Generic:Conn_02x08_Top_Bottom +Connector_Generic:Conn_02x09_Counter_Clockwise +Connector_Generic:Conn_02x09_Odd_Even +Connector_Generic:Conn_02x09_Row_Letter_First +Connector_Generic:Conn_02x09_Row_Letter_Last +Connector_Generic:Conn_02x09_Top_Bottom +Connector_Generic:Conn_02x10_Counter_Clockwise +Connector_Generic:Conn_02x10_Odd_Even +Connector_Generic:Conn_02x10_Row_Letter_First +Connector_Generic:Conn_02x10_Row_Letter_Last +Connector_Generic:Conn_02x10_Top_Bottom +Connector_Generic:Conn_02x11_Counter_Clockwise +Connector_Generic:Conn_02x11_Odd_Even +Connector_Generic:Conn_02x11_Row_Letter_First +Connector_Generic:Conn_02x11_Row_Letter_Last +Connector_Generic:Conn_02x11_Top_Bottom +Connector_Generic:Conn_02x12_Counter_Clockwise +Connector_Generic:Conn_02x12_Odd_Even +Connector_Generic:Conn_02x12_Row_Letter_First +Connector_Generic:Conn_02x12_Row_Letter_Last +Connector_Generic:Conn_02x12_Top_Bottom +Connector_Generic:Conn_02x13_Counter_Clockwise +Connector_Generic:Conn_02x13_Odd_Even +Connector_Generic:Conn_02x13_Row_Letter_First +Connector_Generic:Conn_02x13_Row_Letter_Last +Connector_Generic:Conn_02x13_Top_Bottom +Connector_Generic:Conn_02x14_Counter_Clockwise +Connector_Generic:Conn_02x14_Odd_Even +Connector_Generic:Conn_02x14_Row_Letter_First +Connector_Generic:Conn_02x14_Row_Letter_Last +Connector_Generic:Conn_02x14_Top_Bottom +Connector_Generic:Conn_02x15_Counter_Clockwise +Connector_Generic:Conn_02x15_Odd_Even +Connector_Generic:Conn_02x15_Row_Letter_First +Connector_Generic:Conn_02x15_Row_Letter_Last +Connector_Generic:Conn_02x15_Top_Bottom +Connector_Generic:Conn_02x16_Counter_Clockwise +Connector_Generic:Conn_02x16_Odd_Even +Connector_Generic:Conn_02x16_Row_Letter_First +Connector_Generic:Conn_02x16_Row_Letter_Last +Connector_Generic:Conn_02x16_Top_Bottom +Connector_Generic:Conn_02x17_Counter_Clockwise +Connector_Generic:Conn_02x17_Odd_Even +Connector_Generic:Conn_02x17_Row_Letter_First +Connector_Generic:Conn_02x17_Row_Letter_Last +Connector_Generic:Conn_02x17_Top_Bottom +Connector_Generic:Conn_02x18_Counter_Clockwise +Connector_Generic:Conn_02x18_Odd_Even +Connector_Generic:Conn_02x18_Row_Letter_First +Connector_Generic:Conn_02x18_Row_Letter_Last +Connector_Generic:Conn_02x18_Top_Bottom +Connector_Generic:Conn_02x19_Counter_Clockwise +Connector_Generic:Conn_02x19_Odd_Even +Connector_Generic:Conn_02x19_Row_Letter_First +Connector_Generic:Conn_02x19_Row_Letter_Last +Connector_Generic:Conn_02x19_Top_Bottom +Connector_Generic:Conn_02x20_Counter_Clockwise +Connector_Generic:Conn_02x20_Odd_Even +Connector_Generic:Conn_02x20_Row_Letter_First +Connector_Generic:Conn_02x20_Row_Letter_Last +Connector_Generic:Conn_02x20_Top_Bottom +Connector_Generic:Conn_02x21_Counter_Clockwise +Connector_Generic:Conn_02x21_Odd_Even +Connector_Generic:Conn_02x21_Row_Letter_First +Connector_Generic:Conn_02x21_Row_Letter_Last +Connector_Generic:Conn_02x21_Top_Bottom +Connector_Generic:Conn_02x22_Counter_Clockwise +Connector_Generic:Conn_02x22_Odd_Even +Connector_Generic:Conn_02x22_Row_Letter_First +Connector_Generic:Conn_02x22_Row_Letter_Last +Connector_Generic:Conn_02x22_Top_Bottom +Connector_Generic:Conn_02x23_Counter_Clockwise +Connector_Generic:Conn_02x23_Odd_Even +Connector_Generic:Conn_02x23_Row_Letter_First +Connector_Generic:Conn_02x23_Row_Letter_Last +Connector_Generic:Conn_02x23_Top_Bottom +Connector_Generic:Conn_02x24_Counter_Clockwise +Connector_Generic:Conn_02x24_Odd_Even +Connector_Generic:Conn_02x24_Row_Letter_First +Connector_Generic:Conn_02x24_Row_Letter_Last +Connector_Generic:Conn_02x24_Top_Bottom +Connector_Generic:Conn_02x25_Counter_Clockwise +Connector_Generic:Conn_02x25_Odd_Even +Connector_Generic:Conn_02x25_Row_Letter_First +Connector_Generic:Conn_02x25_Row_Letter_Last +Connector_Generic:Conn_02x25_Top_Bottom +Connector_Generic:Conn_02x26_Counter_Clockwise +Connector_Generic:Conn_02x26_Odd_Even +Connector_Generic:Conn_02x26_Row_Letter_First +Connector_Generic:Conn_02x26_Row_Letter_Last +Connector_Generic:Conn_02x26_Top_Bottom +Connector_Generic:Conn_02x27_Counter_Clockwise +Connector_Generic:Conn_02x27_Odd_Even +Connector_Generic:Conn_02x27_Row_Letter_First +Connector_Generic:Conn_02x27_Row_Letter_Last +Connector_Generic:Conn_02x27_Top_Bottom +Connector_Generic:Conn_02x28_Counter_Clockwise +Connector_Generic:Conn_02x28_Odd_Even +Connector_Generic:Conn_02x28_Row_Letter_First +Connector_Generic:Conn_02x28_Row_Letter_Last +Connector_Generic:Conn_02x28_Top_Bottom +Connector_Generic:Conn_02x29_Counter_Clockwise +Connector_Generic:Conn_02x29_Odd_Even +Connector_Generic:Conn_02x29_Row_Letter_First +Connector_Generic:Conn_02x29_Row_Letter_Last +Connector_Generic:Conn_02x29_Top_Bottom +Connector_Generic:Conn_02x30_Counter_Clockwise +Connector_Generic:Conn_02x30_Odd_Even +Connector_Generic:Conn_02x30_Row_Letter_First +Connector_Generic:Conn_02x30_Row_Letter_Last +Connector_Generic:Conn_02x30_Top_Bottom +Connector_Generic:Conn_02x31_Counter_Clockwise +Connector_Generic:Conn_02x31_Odd_Even +Connector_Generic:Conn_02x31_Row_Letter_First +Connector_Generic:Conn_02x31_Row_Letter_Last +Connector_Generic:Conn_02x31_Top_Bottom +Connector_Generic:Conn_02x32_Counter_Clockwise +Connector_Generic:Conn_02x32_Odd_Even +Connector_Generic:Conn_02x32_Row_Letter_First +Connector_Generic:Conn_02x32_Row_Letter_Last +Connector_Generic:Conn_02x32_Top_Bottom +Connector_Generic:Conn_02x33_Counter_Clockwise +Connector_Generic:Conn_02x33_Odd_Even +Connector_Generic:Conn_02x33_Row_Letter_First +Connector_Generic:Conn_02x33_Row_Letter_Last +Connector_Generic:Conn_02x33_Top_Bottom +Connector_Generic:Conn_02x34_Counter_Clockwise +Connector_Generic:Conn_02x34_Odd_Even +Connector_Generic:Conn_02x34_Row_Letter_First +Connector_Generic:Conn_02x34_Row_Letter_Last +Connector_Generic:Conn_02x34_Top_Bottom +Connector_Generic:Conn_02x35_Counter_Clockwise +Connector_Generic:Conn_02x35_Odd_Even +Connector_Generic:Conn_02x35_Row_Letter_First +Connector_Generic:Conn_02x35_Row_Letter_Last +Connector_Generic:Conn_02x35_Top_Bottom +Connector_Generic:Conn_02x36_Counter_Clockwise +Connector_Generic:Conn_02x36_Odd_Even +Connector_Generic:Conn_02x36_Row_Letter_First +Connector_Generic:Conn_02x36_Row_Letter_Last +Connector_Generic:Conn_02x36_Top_Bottom +Connector_Generic:Conn_02x37_Counter_Clockwise +Connector_Generic:Conn_02x37_Odd_Even +Connector_Generic:Conn_02x37_Row_Letter_First +Connector_Generic:Conn_02x37_Row_Letter_Last +Connector_Generic:Conn_02x37_Top_Bottom +Connector_Generic:Conn_02x38_Counter_Clockwise +Connector_Generic:Conn_02x38_Odd_Even +Connector_Generic:Conn_02x38_Row_Letter_First +Connector_Generic:Conn_02x38_Row_Letter_Last +Connector_Generic:Conn_02x38_Top_Bottom +Connector_Generic:Conn_02x39_Counter_Clockwise +Connector_Generic:Conn_02x39_Odd_Even +Connector_Generic:Conn_02x39_Row_Letter_First +Connector_Generic:Conn_02x39_Row_Letter_Last +Connector_Generic:Conn_02x39_Top_Bottom +Connector_Generic:Conn_02x40_Counter_Clockwise +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 +Connector_Generic:Conn_2Rows-11Pins +Connector_Generic:Conn_2Rows-13Pins +Connector_Generic:Conn_2Rows-15Pins +Connector_Generic:Conn_2Rows-17Pins +Connector_Generic:Conn_2Rows-19Pins +Connector_Generic:Conn_2Rows-21Pins +Connector_Generic:Conn_2Rows-23Pins +Connector_Generic:Conn_2Rows-25Pins +Connector_Generic:Conn_2Rows-27Pins +Connector_Generic:Conn_2Rows-29Pins +Connector_Generic:Conn_2Rows-31Pins +Connector_Generic:Conn_2Rows-33Pins +Connector_Generic:Conn_2Rows-35Pins +Connector_Generic:Conn_2Rows-37Pins +Connector_Generic:Conn_2Rows-39Pins +Connector_Generic:Conn_2Rows-41Pins +Connector_Generic:Conn_2Rows-43Pins +Connector_Generic:Conn_2Rows-45Pins +Connector_Generic:Conn_2Rows-47Pins +Connector_Generic:Conn_2Rows-49Pins +Connector_Generic:Conn_2Rows-51Pins +Connector_Generic:Conn_2Rows-53Pins +Connector_Generic:Conn_2Rows-55Pins +Connector_Generic:Conn_2Rows-57Pins +Connector_Generic:Conn_2Rows-59Pins +Connector_Generic:Conn_2Rows-61Pins +Connector_Generic:Conn_2Rows-63Pins +Connector_Generic:Conn_2Rows-65Pins +Connector_Generic:Conn_2Rows-67Pins +Connector_Generic:Conn_2Rows-69Pins +Connector_Generic:Conn_2Rows-71Pins +Connector_Generic:Conn_2Rows-73Pins +Connector_Generic:Conn_2Rows-75Pins +Connector_Generic_MountingPin:Conn_01x01_MountingPin +Connector_Generic_MountingPin:Conn_01x02_MountingPin +Connector_Generic_MountingPin:Conn_01x03_MountingPin +Connector_Generic_MountingPin:Conn_01x04_MountingPin +Connector_Generic_MountingPin:Conn_01x05_MountingPin +Connector_Generic_MountingPin:Conn_01x06_MountingPin +Connector_Generic_MountingPin:Conn_01x07_MountingPin +Connector_Generic_MountingPin:Conn_01x08_MountingPin +Connector_Generic_MountingPin:Conn_01x09_MountingPin +Connector_Generic_MountingPin:Conn_01x10_MountingPin +Connector_Generic_MountingPin:Conn_01x11_MountingPin +Connector_Generic_MountingPin:Conn_01x12_MountingPin +Connector_Generic_MountingPin:Conn_01x13_MountingPin +Connector_Generic_MountingPin:Conn_01x14_MountingPin +Connector_Generic_MountingPin:Conn_01x15_MountingPin +Connector_Generic_MountingPin:Conn_01x16_MountingPin +Connector_Generic_MountingPin:Conn_01x17_MountingPin +Connector_Generic_MountingPin:Conn_01x18_MountingPin +Connector_Generic_MountingPin:Conn_01x19_MountingPin +Connector_Generic_MountingPin:Conn_01x20_MountingPin +Connector_Generic_MountingPin:Conn_01x21_MountingPin +Connector_Generic_MountingPin:Conn_01x22_MountingPin +Connector_Generic_MountingPin:Conn_01x23_MountingPin +Connector_Generic_MountingPin:Conn_01x24_MountingPin +Connector_Generic_MountingPin:Conn_01x25_MountingPin +Connector_Generic_MountingPin:Conn_01x26_MountingPin +Connector_Generic_MountingPin:Conn_01x27_MountingPin +Connector_Generic_MountingPin:Conn_01x28_MountingPin +Connector_Generic_MountingPin:Conn_01x29_MountingPin +Connector_Generic_MountingPin:Conn_01x30_MountingPin +Connector_Generic_MountingPin:Conn_01x31_MountingPin +Connector_Generic_MountingPin:Conn_01x32_MountingPin +Connector_Generic_MountingPin:Conn_01x33_MountingPin +Connector_Generic_MountingPin:Conn_01x34_MountingPin +Connector_Generic_MountingPin:Conn_01x35_MountingPin +Connector_Generic_MountingPin:Conn_01x36_MountingPin +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 +Connector_Generic_MountingPin:Conn_02x02_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x02_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x02_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x02_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x02_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x03_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x03_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x03_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x03_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x03_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x04_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x04_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x04_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x04_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x04_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x05_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x05_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x05_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x05_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x05_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x06_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x06_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x06_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x06_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x06_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x07_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x07_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x07_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x07_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x07_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x08_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x08_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x08_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x08_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x08_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x09_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x09_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x09_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x09_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x09_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x10_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x10_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x10_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x10_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x10_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x11_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x11_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x11_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x11_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x11_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x12_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x12_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x12_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x12_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x12_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x13_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x13_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x13_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x13_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x13_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x14_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x14_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x14_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x14_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x14_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x15_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x15_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x15_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x15_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x15_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x16_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x16_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x16_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x16_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x16_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x17_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x17_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x17_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x17_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x17_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x18_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x18_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x18_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x18_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x18_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x19_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x19_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x19_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x19_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x19_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x20_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x20_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x20_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x20_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x20_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x21_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x21_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x21_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x21_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x21_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x22_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x22_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x22_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x22_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x22_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x23_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x23_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x23_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x23_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x23_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x24_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x24_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x24_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x24_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x24_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x25_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x25_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x25_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x25_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x25_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x26_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x26_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x26_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x26_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x26_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x27_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x27_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x27_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x27_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x27_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x28_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x28_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x28_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x28_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x28_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x29_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x29_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x29_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x29_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x29_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x30_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x30_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x30_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x30_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x30_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x31_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x31_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x31_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x31_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x31_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x32_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x32_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x32_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x32_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x32_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x33_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x33_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x33_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x33_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x33_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x34_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x34_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x34_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x34_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x34_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x35_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x35_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x35_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x35_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x35_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x36_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x36_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x36_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x36_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x36_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x37_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x37_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x37_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x37_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x37_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x38_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x38_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x38_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x38_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x38_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x39_Counter_Clockwise_MountingPin +Connector_Generic_MountingPin:Conn_02x39_Odd_Even_MountingPin +Connector_Generic_MountingPin:Conn_02x39_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x39_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x39_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x40_Counter_Clockwise_MountingPin +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 +Connector_Generic_MountingPin:Conn_2Rows-11Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-13Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-15Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-17Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-19Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-21Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-23Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-25Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-27Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-29Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-31Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-33Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-35Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-37Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-39Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-41Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-43Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-45Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-47Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-49Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-51Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-53Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-55Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-57Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-59Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-61Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-63Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-65Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-67Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-69Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-71Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-73Pins_MountingPin +Connector_Generic_MountingPin:Conn_2Rows-75Pins_MountingPin +Connector_Generic_Shielded:Conn_01x01_Shielded +Connector_Generic_Shielded:Conn_01x02_Shielded +Connector_Generic_Shielded:Conn_01x03_Shielded +Connector_Generic_Shielded:Conn_01x04_Shielded +Connector_Generic_Shielded:Conn_01x05_Shielded +Connector_Generic_Shielded:Conn_01x06_Shielded +Connector_Generic_Shielded:Conn_01x07_Shielded +Connector_Generic_Shielded:Conn_01x08_Shielded +Connector_Generic_Shielded:Conn_01x09_Shielded +Connector_Generic_Shielded:Conn_01x10_Shielded +Connector_Generic_Shielded:Conn_01x11_Shielded +Connector_Generic_Shielded:Conn_01x12_Shielded +Connector_Generic_Shielded:Conn_01x13_Shielded +Connector_Generic_Shielded:Conn_01x14_Shielded +Connector_Generic_Shielded:Conn_01x15_Shielded +Connector_Generic_Shielded:Conn_01x16_Shielded +Connector_Generic_Shielded:Conn_01x17_Shielded +Connector_Generic_Shielded:Conn_01x18_Shielded +Connector_Generic_Shielded:Conn_01x19_Shielded +Connector_Generic_Shielded:Conn_01x20_Shielded +Connector_Generic_Shielded:Conn_01x21_Shielded +Connector_Generic_Shielded:Conn_01x22_Shielded +Connector_Generic_Shielded:Conn_01x23_Shielded +Connector_Generic_Shielded:Conn_01x24_Shielded +Connector_Generic_Shielded:Conn_01x25_Shielded +Connector_Generic_Shielded:Conn_01x26_Shielded +Connector_Generic_Shielded:Conn_01x27_Shielded +Connector_Generic_Shielded:Conn_01x28_Shielded +Connector_Generic_Shielded:Conn_01x29_Shielded +Connector_Generic_Shielded:Conn_01x30_Shielded +Connector_Generic_Shielded:Conn_01x31_Shielded +Connector_Generic_Shielded:Conn_01x32_Shielded +Connector_Generic_Shielded:Conn_01x33_Shielded +Connector_Generic_Shielded:Conn_01x34_Shielded +Connector_Generic_Shielded:Conn_01x35_Shielded +Connector_Generic_Shielded:Conn_01x36_Shielded +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 +Connector_Generic_Shielded:Conn_02x02_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x02_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x02_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x02_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x02_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x03_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x03_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x03_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x03_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x03_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x04_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x04_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x04_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x04_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x04_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x05_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x05_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x05_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x05_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x05_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x06_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x06_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x06_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x06_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x06_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x07_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x07_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x07_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x07_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x07_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x08_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x08_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x08_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x08_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x08_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x09_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x09_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x09_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x09_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x09_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x10_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x10_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x10_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x10_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x10_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x11_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x11_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x11_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x11_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x11_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x12_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x12_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x12_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x12_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x12_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x13_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x13_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x13_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x13_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x13_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x14_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x14_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x14_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x14_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x14_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x15_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x15_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x15_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x15_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x15_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x16_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x16_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x16_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x16_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x16_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x17_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x17_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x17_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x17_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x17_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x18_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x18_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x18_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x18_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x18_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x19_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x19_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x19_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x19_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x19_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x20_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x20_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x20_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x20_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x20_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x21_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x21_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x21_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x21_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x21_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x22_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x22_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x22_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x22_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x22_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x23_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x23_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x23_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x23_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x23_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x24_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x24_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x24_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x24_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x24_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x25_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x25_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x25_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x25_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x25_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x26_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x26_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x26_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x26_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x26_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x27_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x27_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x27_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x27_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x27_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x28_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x28_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x28_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x28_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x28_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x29_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x29_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x29_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x29_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x29_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x30_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x30_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x30_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x30_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x30_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x31_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x31_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x31_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x31_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x31_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x32_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x32_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x32_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x32_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x32_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x33_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x33_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x33_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x33_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x33_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x34_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x34_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x34_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x34_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x34_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x35_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x35_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x35_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x35_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x35_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x36_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x36_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x36_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x36_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x36_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x37_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x37_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x37_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x37_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x37_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x38_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x38_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x38_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x38_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x38_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x39_Counter_Clockwise_Shielded +Connector_Generic_Shielded:Conn_02x39_Odd_Even_Shielded +Connector_Generic_Shielded:Conn_02x39_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x39_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x39_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x40_Counter_Clockwise_Shielded +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 +Connector_Generic_Shielded:Conn_2Rows-11Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-13Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-15Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-17Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-19Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-21Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-23Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-25Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-27Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-29Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-31Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-33Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-35Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-37Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-39Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-41Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-43Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-45Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-47Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-49Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-51Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-53Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-55Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-57Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-59Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-61Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-63Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-65Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-67Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-69Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-71Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-73Pins_Shielded +Connector_Generic_Shielded:Conn_2Rows-75Pins_Shielded +Converter_ACDC:BAC05S05DC +Converter_ACDC:HLK-10M03 +Converter_ACDC:HLK-10M05 +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 +Converter_ACDC:HLK-5M12 +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 +Converter_ACDC:HS-40009 +Converter_ACDC:HS-40012 +Converter_ACDC:HS-40015 +Converter_ACDC:HS-40018 +Converter_ACDC:HS-40024 +Converter_ACDC:IRM-02-12 +Converter_ACDC:IRM-02-12S +Converter_ACDC:IRM-02-15 +Converter_ACDC:IRM-02-15S +Converter_ACDC:IRM-02-24 +Converter_ACDC:IRM-02-24S +Converter_ACDC:IRM-02-3.3 +Converter_ACDC:IRM-02-3.3S +Converter_ACDC:IRM-02-5 +Converter_ACDC:IRM-02-5S +Converter_ACDC:IRM-02-9 +Converter_ACDC:IRM-02-9S +Converter_ACDC:IRM-03-12 +Converter_ACDC:IRM-03-12S +Converter_ACDC:IRM-03-15 +Converter_ACDC:IRM-03-15S +Converter_ACDC:IRM-03-24 +Converter_ACDC:IRM-03-24S +Converter_ACDC:IRM-03-3.3 +Converter_ACDC:IRM-03-3.3S +Converter_ACDC:IRM-03-5 +Converter_ACDC:IRM-03-5S +Converter_ACDC:IRM-03-9 +Converter_ACDC:IRM-03-9S +Converter_ACDC:IRM-05-12 +Converter_ACDC:IRM-05-15 +Converter_ACDC:IRM-05-24 +Converter_ACDC:IRM-05-3.3 +Converter_ACDC:IRM-05-5 +Converter_ACDC:IRM-10-12 +Converter_ACDC:IRM-10-15 +Converter_ACDC:IRM-10-24 +Converter_ACDC:IRM-10-3.3 +Converter_ACDC:IRM-10-5 +Converter_ACDC:IRM-20-12 +Converter_ACDC:IRM-20-15 +Converter_ACDC:IRM-20-24 +Converter_ACDC:IRM-20-3.3 +Converter_ACDC:IRM-20-5 +Converter_ACDC:IRM-60-12 +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 +Converter_ACDC:PBO-3-S3.3 +Converter_ACDC:PBO-3-S5 +Converter_ACDC:PBO-3-S9 +Converter_ACDC:RAC01-05SGB +Converter_ACDC:RAC01-12SGB +Converter_ACDC:RAC01-24SGB +Converter_ACDC:RAC01-3.3SGB +Converter_ACDC:RAC01-xxSGB +Converter_ACDC:RAC04-05SGA +Converter_ACDC:RAC04-05SGB +Converter_ACDC:RAC04-09SGA +Converter_ACDC:RAC04-09SGB +Converter_ACDC:RAC04-12SGA +Converter_ACDC:RAC04-12SGB +Converter_ACDC:RAC04-15SGA +Converter_ACDC:RAC04-15SGB +Converter_ACDC:RAC04-24SGA +Converter_ACDC:RAC04-24SGB +Converter_ACDC:RAC04-3.3SGA +Converter_ACDC:RAC04-3.3SGB +Converter_ACDC:RAC04-xxSGA +Converter_ACDC:RAC04-xxSGB +Converter_ACDC:RAC05-05SK +Converter_ACDC:RAC05-12SK +Converter_ACDC:RAC05-15SK +Converter_ACDC:RAC05-24SK +Converter_ACDC:RAC05-3.3SK +Converter_ACDC:RAC20-05SK +Converter_ACDC:RAC20-12DK +Converter_ACDC:RAC20-12SK +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 +Converter_ACDC:TMLM04112 +Converter_ACDC:TMLM04115 +Converter_ACDC:TMLM04124 +Converter_ACDC:TMLM04225 +Converter_ACDC:TMLM04253 +Converter_ACDC:TMLM05103 +Converter_ACDC:TMLM05105 +Converter_ACDC:TMLM05112 +Converter_ACDC:TMLM05115 +Converter_ACDC:TMLM05124 +Converter_ACDC:TMLM10103 +Converter_ACDC:TMLM10105 +Converter_ACDC:TMLM10112 +Converter_ACDC:TMLM10115 +Converter_ACDC:TMLM10124 +Converter_ACDC:TMLM20103 +Converter_ACDC:TMLM20105 +Converter_ACDC:TMLM20112 +Converter_ACDC:TMLM20115 +Converter_ACDC:TMLM20124 +Converter_ACDC:TPP-15-103-D +Converter_ACDC:TPP-15-105-D +Converter_ACDC:TPP-15-109-D +Converter_ACDC:TPP-15-112-D +Converter_ACDC:TPP-15-115-D +Converter_ACDC:TPP-15-124-D +Converter_ACDC:TPP-15-136-D +Converter_ACDC:TPP-15-148-D +Converter_ACDC:VTX-214-010-103 +Converter_ACDC:VTX-214-010-105 +Converter_ACDC:VTX-214-010-106 +Converter_ACDC:VTX-214-010-107 +Converter_ACDC:VTX-214-010-108 +Converter_ACDC:VTX-214-010-109 +Converter_ACDC:VTX-214-010-110 +Converter_ACDC:VTX-214-010-112 +Converter_ACDC:VTX-214-010-115 +Converter_ACDC:VTX-214-010-118 +Converter_ACDC:VTX-214-010-124 +Converter_ACDC:VTX-214-010-148 +Converter_ACDC:VTX-214-015-103 +Converter_ACDC:VTX-214-015-105 +Converter_ACDC:VTX-214-015-106 +Converter_ACDC:VTX-214-015-107 +Converter_ACDC:VTX-214-015-109 +Converter_ACDC:VTX-214-015-112 +Converter_ACDC:VTX-214-015-115 +Converter_ACDC:VTX-214-015-118 +Converter_ACDC:VTX-214-015-124 +Converter_ACDC:VTX-214-015-148 +Converter_DCDC:ATA00A18S-L +Converter_DCDC:ATA00A36S-L +Converter_DCDC:ATA00AA18S-L +Converter_DCDC:ATA00AA36S-L +Converter_DCDC:ATA00B18S-L +Converter_DCDC:ATA00B36S-L +Converter_DCDC:ATA00BB18S-L +Converter_DCDC:ATA00BB36S-L +Converter_DCDC:ATA00C18S-L +Converter_DCDC:ATA00C36S-L +Converter_DCDC:ATA00CC18S-L +Converter_DCDC:ATA00CC36S-L +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 +Converter_DCDC:IA0503D +Converter_DCDC:IA0503S +Converter_DCDC:IA0505D +Converter_DCDC:IA0505S +Converter_DCDC:IA0509D +Converter_DCDC:IA0509S +Converter_DCDC:IA0512D +Converter_DCDC:IA0512S +Converter_DCDC:IA0515D +Converter_DCDC:IA0515S +Converter_DCDC:IA0524D +Converter_DCDC:IA0524S +Converter_DCDC:IA1203D +Converter_DCDC:IA1203S +Converter_DCDC:IA1205D +Converter_DCDC:IA1205S +Converter_DCDC:IA1209D +Converter_DCDC:IA1209S +Converter_DCDC:IA1212D +Converter_DCDC:IA1212S +Converter_DCDC:IA1215D +Converter_DCDC:IA1215S +Converter_DCDC:IA1224D +Converter_DCDC:IA1224S +Converter_DCDC:IA2403D +Converter_DCDC:IA2403S +Converter_DCDC:IA2405D +Converter_DCDC:IA2405S +Converter_DCDC:IA2409D +Converter_DCDC:IA2409S +Converter_DCDC:IA2412D +Converter_DCDC:IA2412S +Converter_DCDC:IA2415D +Converter_DCDC:IA2415S +Converter_DCDC:IA2424D +Converter_DCDC:IA2424S +Converter_DCDC:IA4803D +Converter_DCDC:IA4803S +Converter_DCDC:IA4805D +Converter_DCDC:IA4805S +Converter_DCDC:IA4809D +Converter_DCDC:IA4809S +Converter_DCDC:IA4812D +Converter_DCDC:IA4812S +Converter_DCDC:IA4815D +Converter_DCDC:IA4815S +Converter_DCDC:IA4824D +Converter_DCDC:IA4824S +Converter_DCDC:IH0503D +Converter_DCDC:IH0503DH +Converter_DCDC:IH0503S +Converter_DCDC:IH0503SH +Converter_DCDC:IH0505D +Converter_DCDC:IH0505DH +Converter_DCDC:IH0505S +Converter_DCDC:IH0505SH +Converter_DCDC:IH0509D +Converter_DCDC:IH0509DH +Converter_DCDC:IH0509S +Converter_DCDC:IH0509SH +Converter_DCDC:IH0512D +Converter_DCDC:IH0512DH +Converter_DCDC:IH0512S +Converter_DCDC:IH0512SH +Converter_DCDC:IH0515D +Converter_DCDC:IH0515DH +Converter_DCDC:IH0515S +Converter_DCDC:IH0515SH +Converter_DCDC:IH0524D +Converter_DCDC:IH0524DH +Converter_DCDC:IH0524S +Converter_DCDC:IH0524SH +Converter_DCDC:IH1203D +Converter_DCDC:IH1203DH +Converter_DCDC:IH1203S +Converter_DCDC:IH1203SH +Converter_DCDC:IH1205D +Converter_DCDC:IH1205DH +Converter_DCDC:IH1205S +Converter_DCDC:IH1205SH +Converter_DCDC:IH1209D +Converter_DCDC:IH1209DH +Converter_DCDC:IH1209S +Converter_DCDC:IH1209SH +Converter_DCDC:IH1212D +Converter_DCDC:IH1212DH +Converter_DCDC:IH1212S +Converter_DCDC:IH1212SH +Converter_DCDC:IH1215D +Converter_DCDC:IH1215DH +Converter_DCDC:IH1215S +Converter_DCDC:IH1215SH +Converter_DCDC:IH1224D +Converter_DCDC:IH1224DH +Converter_DCDC:IH1224S +Converter_DCDC:IH1224SH +Converter_DCDC:IH2403D +Converter_DCDC:IH2403DH +Converter_DCDC:IH2403S +Converter_DCDC:IH2403SH +Converter_DCDC:IH2405D +Converter_DCDC:IH2405DH +Converter_DCDC:IH2405S +Converter_DCDC:IH2405SH +Converter_DCDC:IH2409D +Converter_DCDC:IH2409DH +Converter_DCDC:IH2409S +Converter_DCDC:IH2409SH +Converter_DCDC:IH2412D +Converter_DCDC:IH2412DH +Converter_DCDC:IH2412S +Converter_DCDC:IH2412SH +Converter_DCDC:IH2415D +Converter_DCDC:IH2415DH +Converter_DCDC:IH2415S +Converter_DCDC:IH2415SH +Converter_DCDC:IH2424D +Converter_DCDC:IH2424DH +Converter_DCDC:IH2424S +Converter_DCDC:IH2424SH +Converter_DCDC:IH4803D +Converter_DCDC:IH4803DH +Converter_DCDC:IH4803S +Converter_DCDC:IH4803SH +Converter_DCDC:IH4805D +Converter_DCDC:IH4805DH +Converter_DCDC:IH4805S +Converter_DCDC:IH4805SH +Converter_DCDC:IH4809D +Converter_DCDC:IH4809DH +Converter_DCDC:IH4809S +Converter_DCDC:IH4809SH +Converter_DCDC:IH4812D +Converter_DCDC:IH4812DH +Converter_DCDC:IH4812S +Converter_DCDC:IH4812SH +Converter_DCDC:IH4815D +Converter_DCDC:IH4815DH +Converter_DCDC:IH4815S +Converter_DCDC:IH4815SH +Converter_DCDC:IH4824D +Converter_DCDC:IH4824DH +Converter_DCDC:IH4824S +Converter_DCDC:IH4824SH +Converter_DCDC:ISU0205D12 +Converter_DCDC:ISU0205D15 +Converter_DCDC:ISU0205S05 +Converter_DCDC:ISU0205S12 +Converter_DCDC:ISU0205S15 +Converter_DCDC:ISU0205S24 +Converter_DCDC:ISU0224D12 +Converter_DCDC:ISU0224D15 +Converter_DCDC:ISU0224S05 +Converter_DCDC:ISU0224S12 +Converter_DCDC:ISU0224S15 +Converter_DCDC:ISU0224S24 +Converter_DCDC:ISU0248D12 +Converter_DCDC:ISU0248D15 +Converter_DCDC:ISU0248S05 +Converter_DCDC:ISU0248S12 +Converter_DCDC:ISU0248S15 +Converter_DCDC:ISU0248S24 +Converter_DCDC:ITQ2403SA +Converter_DCDC:ITQ2403SA-H +Converter_DCDC:ITQ2405S +Converter_DCDC:ITQ2405S-H +Converter_DCDC:ITQ2405SA +Converter_DCDC:ITQ2405SA-H +Converter_DCDC:ITQ2409SA +Converter_DCDC:ITQ2409SA-H +Converter_DCDC:ITQ2412S +Converter_DCDC:ITQ2412S-H +Converter_DCDC:ITQ2412SA +Converter_DCDC:ITQ2412SA-H +Converter_DCDC:ITQ2415S +Converter_DCDC:ITQ2415S-H +Converter_DCDC:ITQ2415SA +Converter_DCDC:ITQ2415SA-H +Converter_DCDC:ITQ2424SA +Converter_DCDC:ITQ2424SA-H +Converter_DCDC:ITQ4803SA +Converter_DCDC:ITQ4803SA-H +Converter_DCDC:ITQ4805S +Converter_DCDC:ITQ4805S-H +Converter_DCDC:ITQ4805SA +Converter_DCDC:ITQ4805SA-H +Converter_DCDC:ITQ4809SA +Converter_DCDC:ITQ4809SA-H +Converter_DCDC:ITQ4812S +Converter_DCDC:ITQ4812S-H +Converter_DCDC:ITQ4812SA +Converter_DCDC:ITQ4812SA-H +Converter_DCDC:ITQ4815S +Converter_DCDC:ITQ4815S-H +Converter_DCDC:ITQ4815SA +Converter_DCDC:ITQ4815SA-H +Converter_DCDC:ITQ4824SA +Converter_DCDC:ITQ4824SA-H +Converter_DCDC:ITX0503SA +Converter_DCDC:ITX0503SA-H +Converter_DCDC:ITX0503SA-HR +Converter_DCDC:ITX0503SA-R +Converter_DCDC:ITX0505S +Converter_DCDC:ITX0505S-H +Converter_DCDC:ITX0505S-HR +Converter_DCDC:ITX0505S-R +Converter_DCDC:ITX0505SA +Converter_DCDC:ITX0505SA-H +Converter_DCDC:ITX0505SA-HR +Converter_DCDC:ITX0505SA-R +Converter_DCDC:ITX0509SA +Converter_DCDC:ITX0509SA-H +Converter_DCDC:ITX0509SA-HR +Converter_DCDC:ITX0509SA-R +Converter_DCDC:ITX0512S +Converter_DCDC:ITX0512S-H +Converter_DCDC:ITX0512S-HR +Converter_DCDC:ITX0512S-R +Converter_DCDC:ITX0512SA +Converter_DCDC:ITX0512SA-H +Converter_DCDC:ITX0512SA-HR +Converter_DCDC:ITX0512SA-R +Converter_DCDC:ITX0515S +Converter_DCDC:ITX0515S-H +Converter_DCDC:ITX0515S-HR +Converter_DCDC:ITX0515S-R +Converter_DCDC:ITX0515SA +Converter_DCDC:ITX0515SA-H +Converter_DCDC:ITX0515SA-HR +Converter_DCDC:ITX0515SA-R +Converter_DCDC:ITX0524SA +Converter_DCDC:ITX0524SA-H +Converter_DCDC:ITX0524SA-HR +Converter_DCDC:ITX0524SA-R +Converter_DCDC:ITX1203SA +Converter_DCDC:ITX1203SA-H +Converter_DCDC:ITX1203SA-HR +Converter_DCDC:ITX1203SA-R +Converter_DCDC:ITX1205S +Converter_DCDC:ITX1205S-H +Converter_DCDC:ITX1205S-HR +Converter_DCDC:ITX1205S-R +Converter_DCDC:ITX1205SA +Converter_DCDC:ITX1205SA-H +Converter_DCDC:ITX1205SA-HR +Converter_DCDC:ITX1205SA-R +Converter_DCDC:ITX1209SA +Converter_DCDC:ITX1209SA-H +Converter_DCDC:ITX1209SA-HR +Converter_DCDC:ITX1209SA-R +Converter_DCDC:ITX1212S +Converter_DCDC:ITX1212S-H +Converter_DCDC:ITX1212S-HR +Converter_DCDC:ITX1212S-R +Converter_DCDC:ITX1212SA +Converter_DCDC:ITX1212SA-H +Converter_DCDC:ITX1212SA-HR +Converter_DCDC:ITX1212SA-R +Converter_DCDC:ITX1215S +Converter_DCDC:ITX1215S-H +Converter_DCDC:ITX1215S-HR +Converter_DCDC:ITX1215S-R +Converter_DCDC:ITX1215SA +Converter_DCDC:ITX1215SA-H +Converter_DCDC:ITX1215SA-HR +Converter_DCDC:ITX1215SA-R +Converter_DCDC:ITX1224SA +Converter_DCDC:ITX1224SA-H +Converter_DCDC:ITX1224SA-HR +Converter_DCDC:ITX1224SA-R +Converter_DCDC:ITX2403SA +Converter_DCDC:ITX2403SA-H +Converter_DCDC:ITX2403SA-HR +Converter_DCDC:ITX2403SA-R +Converter_DCDC:ITX2405S +Converter_DCDC:ITX2405S-H +Converter_DCDC:ITX2405S-HR +Converter_DCDC:ITX2405S-R +Converter_DCDC:ITX2405SA +Converter_DCDC:ITX2405SA-H +Converter_DCDC:ITX2405SA-HR +Converter_DCDC:ITX2405SA-R +Converter_DCDC:ITX2409SA +Converter_DCDC:ITX2409SA-H +Converter_DCDC:ITX2409SA-HR +Converter_DCDC:ITX2409SA-R +Converter_DCDC:ITX2412S +Converter_DCDC:ITX2412S-H +Converter_DCDC:ITX2412S-HR +Converter_DCDC:ITX2412S-R +Converter_DCDC:ITX2412SA +Converter_DCDC:ITX2412SA-H +Converter_DCDC:ITX2412SA-HR +Converter_DCDC:ITX2412SA-R +Converter_DCDC:ITX2415S +Converter_DCDC:ITX2415S-H +Converter_DCDC:ITX2415S-HR +Converter_DCDC:ITX2415S-R +Converter_DCDC:ITX2415SA +Converter_DCDC:ITX2415SA-H +Converter_DCDC:ITX2415SA-HR +Converter_DCDC:ITX2415SA-R +Converter_DCDC:ITX2424SA +Converter_DCDC:ITX2424SA-H +Converter_DCDC:ITX2424SA-HR +Converter_DCDC:ITX2424SA-R +Converter_DCDC:ITX4803SA +Converter_DCDC:ITX4803SA-H +Converter_DCDC:ITX4803SA-HR +Converter_DCDC:ITX4803SA-R +Converter_DCDC:ITX4805S +Converter_DCDC:ITX4805S-H +Converter_DCDC:ITX4805S-HR +Converter_DCDC:ITX4805S-R +Converter_DCDC:ITX4805SA +Converter_DCDC:ITX4805SA-H +Converter_DCDC:ITX4805SA-HR +Converter_DCDC:ITX4805SA-R +Converter_DCDC:ITX4809SA +Converter_DCDC:ITX4809SA-H +Converter_DCDC:ITX4809SA-HR +Converter_DCDC:ITX4809SA-R +Converter_DCDC:ITX4812S +Converter_DCDC:ITX4812S-H +Converter_DCDC:ITX4812S-HR +Converter_DCDC:ITX4812S-R +Converter_DCDC:ITX4812SA +Converter_DCDC:ITX4812SA-H +Converter_DCDC:ITX4812SA-HR +Converter_DCDC:ITX4812SA-R +Converter_DCDC:ITX4815S +Converter_DCDC:ITX4815S-H +Converter_DCDC:ITX4815S-HR +Converter_DCDC:ITX4815S-R +Converter_DCDC:ITX4815SA +Converter_DCDC:ITX4815SA-H +Converter_DCDC:ITX4815SA-HR +Converter_DCDC:ITX4815SA-R +Converter_DCDC:ITX4824SA +Converter_DCDC:ITX4824SA-H +Converter_DCDC:ITX4824SA-HR +Converter_DCDC:ITX4824SA-R +Converter_DCDC:JTD1524D05 +Converter_DCDC:JTD1524D12 +Converter_DCDC:JTD1524D15 +Converter_DCDC:JTD1524S05 +Converter_DCDC:JTD1524S12 +Converter_DCDC:JTD1524S15 +Converter_DCDC:JTD1524S3V3 +Converter_DCDC:JTD1548D05 +Converter_DCDC:JTD1548D12 +Converter_DCDC:JTD1548D15 +Converter_DCDC:JTD1548S05 +Converter_DCDC:JTD1548S12 +Converter_DCDC:JTD1548S15 +Converter_DCDC:JTD1548S3V3 +Converter_DCDC:JTD2024D05 +Converter_DCDC:JTD2024D12 +Converter_DCDC:JTD2024D15 +Converter_DCDC:JTD2024S05 +Converter_DCDC:JTD2024S12 +Converter_DCDC:JTD2024S15 +Converter_DCDC:JTD2024S3V3 +Converter_DCDC:JTD2048D05 +Converter_DCDC:JTD2048D12 +Converter_DCDC:JTD2048D15 +Converter_DCDC:JTD2048S05 +Converter_DCDC:JTD2048S12 +Converter_DCDC:JTD2048S15 +Converter_DCDC:JTD2048S3V3 +Converter_DCDC:JTE0624D03 +Converter_DCDC:JTE0624D05 +Converter_DCDC:JTE0624D12 +Converter_DCDC:JTE0624D15 +Converter_DCDC:JTE0624D24 +Converter_DCDC:LT1026 +Converter_DCDC:MEE1S0303SC +Converter_DCDC:MEE1S0305SC +Converter_DCDC:MEE1S0309SC +Converter_DCDC:MEE1S0312SC +Converter_DCDC:MEE1S0315SC +Converter_DCDC:MEE1S0503SC +Converter_DCDC:MEE1S0505SC +Converter_DCDC:MEE1S0509SC +Converter_DCDC:MEE1S0512SC +Converter_DCDC:MEE1S0515SC +Converter_DCDC:MEE1S1205SC +Converter_DCDC:MEE1S1209SC +Converter_DCDC:MEE1S1212SC +Converter_DCDC:MEE1S1215SC +Converter_DCDC:MEE1S1505SC +Converter_DCDC:MEE1S1509SC +Converter_DCDC:MEE1S1512SC +Converter_DCDC:MEE1S1515SC +Converter_DCDC:MEE1S2405SC +Converter_DCDC:MEE1S2409SC +Converter_DCDC:MEE1S2412SC +Converter_DCDC:MEE1S2415SC +Converter_DCDC:MEE3S0505SC +Converter_DCDC:MEE3S0509SC +Converter_DCDC:MEE3S0512SC +Converter_DCDC:MEE3S0515SC +Converter_DCDC:MEE3S1205SC +Converter_DCDC:MEE3S1209SC +Converter_DCDC:MEE3S1212SC +Converter_DCDC:MEE3S1215SC +Converter_DCDC:MGJ2D051505SC +Converter_DCDC:MGJ2D051509SC +Converter_DCDC:MGJ2D051515SC +Converter_DCDC:MGJ2D051802SC +Converter_DCDC:MGJ2D052003SC +Converter_DCDC:MGJ2D052005SC +Converter_DCDC:MGJ2D121505SC +Converter_DCDC:MGJ2D121509SC +Converter_DCDC:MGJ2D121802SC +Converter_DCDC:MGJ2D122003SC +Converter_DCDC:MGJ2D122005SC +Converter_DCDC:MGJ2D151505SC +Converter_DCDC:MGJ2D151509SC +Converter_DCDC:MGJ2D151515SC +Converter_DCDC:MGJ2D151802SC +Converter_DCDC:MGJ2D152003SC +Converter_DCDC:MGJ2D152005SC +Converter_DCDC:MGJ2D241505SC +Converter_DCDC:MGJ2D241509SC +Converter_DCDC:MGJ2D241709SC +Converter_DCDC:MGJ2D241802SC +Converter_DCDC:MGJ2D242003SC +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 +Converter_DCDC:OKI-78SR-3.3_1.5-W36H-C +Converter_DCDC:OKI-78SR-5_1.5-W36-C +Converter_DCDC:OKI-78SR-5_1.5-W36H-C +Converter_DCDC:PTN78000H_EUS-5 +Converter_DCDC:PTN78000W_EUS-5 +Converter_DCDC:PTN78020H_EUK-7 +Converter_DCDC:PTN78020W_EUK-7 +Converter_DCDC:PTN78060H_EUW-7 +Converter_DCDC:PTN78060W_EUW-7 +Converter_DCDC:RPA60-2405SFW +Converter_DCDC:RPA60-2412SFW +Converter_DCDC:RPA60-2415SFW +Converter_DCDC:RPA60-2424SFW +Converter_DCDC:RPM3.3-1.0 +Converter_DCDC:RPM3.3-2.0 +Converter_DCDC:RPM3.3-3.0 +Converter_DCDC:RPM3.3-6.0 +Converter_DCDC:RPM5.0-1.0 +Converter_DCDC:RPM5.0-2.0 +Converter_DCDC:RPM5.0-3.0 +Converter_DCDC:RPM5.0-6.0 +Converter_DCDC:RPMH12-1.5 +Converter_DCDC:RPMH15-1.5 +Converter_DCDC:RPMH24-1.5 +Converter_DCDC:RPMH3.3-1.5 +Converter_DCDC:RPMH5.0-1.5 +Converter_DCDC:TBA1-0511E +Converter_DCDC:TBA1-0512E +Converter_DCDC:TBA1-0513E +Converter_DCDC:TBA1-0521E +Converter_DCDC:TBA1-0522E +Converter_DCDC:TBA1-0523E +Converter_DCDC:TBA1-1211E +Converter_DCDC:TBA1-1212E +Converter_DCDC:TBA1-1213E +Converter_DCDC:TBA1-1221E +Converter_DCDC:TBA1-1222E +Converter_DCDC:TBA1-1223E +Converter_DCDC:TBA1-2411E +Converter_DCDC:TBA1-2412E +Converter_DCDC:TBA1-2413E +Converter_DCDC:TBA1-2421E +Converter_DCDC:TBA1-2422E +Converter_DCDC:TBA1-2423E +Converter_DCDC:TBA2-0511 +Converter_DCDC:TBA2-0512 +Converter_DCDC:TBA2-0513 +Converter_DCDC:TBA2-0521 +Converter_DCDC:TBA2-0522 +Converter_DCDC:TBA2-0523 +Converter_DCDC:TBA2-1211 +Converter_DCDC:TBA2-1212 +Converter_DCDC:TBA2-1213 +Converter_DCDC:TBA2-1221 +Converter_DCDC:TBA2-1222 +Converter_DCDC:TBA2-1223 +Converter_DCDC:TBA2-2411 +Converter_DCDC:TBA2-2412 +Converter_DCDC:TBA2-2413 +Converter_DCDC:TBA2-2421 +Converter_DCDC:TBA2-2422 +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 +Converter_DCDC:TEC2-1210WI +Converter_DCDC:TEC2-1211WI +Converter_DCDC:TEC2-1212WI +Converter_DCDC:TEC2-1213WI +Converter_DCDC:TEC2-1215WI +Converter_DCDC:TEC2-1219WI +Converter_DCDC:TEC2-2410WI +Converter_DCDC:TEC2-2411WI +Converter_DCDC:TEC2-2412WI +Converter_DCDC:TEC2-2413WI +Converter_DCDC:TEC2-2415WI +Converter_DCDC:TEC2-2419WI +Converter_DCDC:TEC2-4810WI +Converter_DCDC:TEC2-4811WI +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 +Converter_DCDC:TEN20-2413WIN +Converter_DCDC:TEN20-2421WIN +Converter_DCDC:TEN20-2422WIN +Converter_DCDC:TEN20-2423WIN +Converter_DCDC:TEN20-4810WIN +Converter_DCDC:TEN20-4811WIN +Converter_DCDC:TEN20-4812WIN +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 +Converter_DCDC:TMA-0512S +Converter_DCDC:TMA-0515D +Converter_DCDC:TMA-0515S +Converter_DCDC:TMA-1205D +Converter_DCDC:TMA-1205S +Converter_DCDC:TMA-1212D +Converter_DCDC:TMA-1212S +Converter_DCDC:TMA-1215D +Converter_DCDC:TMA-1215S +Converter_DCDC:TMA-1505D +Converter_DCDC:TMA-1505S +Converter_DCDC:TMA-1512D +Converter_DCDC:TMA-1512S +Converter_DCDC:TMA-1515D +Converter_DCDC:TMA-1515S +Converter_DCDC:TMA-2405D +Converter_DCDC:TMA-2405S +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 +Converter_DCDC:TMR-1210 +Converter_DCDC:TMR-1211 +Converter_DCDC:TMR-1212 +Converter_DCDC:TMR-2410 +Converter_DCDC:TMR-2411 +Converter_DCDC:TMR-2412 +Converter_DCDC:TMR-4810 +Converter_DCDC:TMR-4811 +Converter_DCDC:TMR-4812 +Converter_DCDC:TMR2-2410WI +Converter_DCDC:TMR2-2411WI +Converter_DCDC:TMR2-2412WI +Converter_DCDC:TMR2-2413WI +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 +Converter_DCDC:TMU3-1211 +Converter_DCDC:TMU3-1212 +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 +Converter_DCDC:TRI1-1211 +Converter_DCDC:TRI1-1212 +Converter_DCDC:TRI1-1213 +Converter_DCDC:TRI1-2411 +Converter_DCDC:TRI1-2412 +Converter_DCDC:TRI1-2413 +CPLD_Altera:EP1210 +CPLD_Altera:EP1810 +CPLD_Altera:EP300 +CPLD_Altera:EP310 +CPLD_Altera:EP320 +CPLD_Altera:EP600 +CPLD_Altera:EP910 +CPLD_Altera:EPM1270F256 +CPLD_Altera:EPM1270M256 +CPLD_Altera:EPM1270T144 +CPLD_Altera:EPM2210F256 +CPLD_Altera:EPM2210F324 +CPLD_Altera:EPM240F100 +CPLD_Altera:EPM240M100 +CPLD_Altera:EPM240T100 +CPLD_Altera:EPM240ZM100 +CPLD_Altera:EPM240ZM68 +CPLD_Altera:EPM570F100 +CPLD_Altera:EPM570F256 +CPLD_Altera:EPM570M100 +CPLD_Altera:EPM570M256 +CPLD_Altera:EPM570T100 +CPLD_Altera:EPM570T144 +CPLD_Altera:EPM570ZM100 +CPLD_Altera:EPM570ZM144 +CPLD_Altera:EPM570ZM256 +CPLD_Microchip:ATF1502AS-xAx44 +CPLD_Microchip:ATF1502ASL-xAx44 +CPLD_Microchip:ATF1502ASV-xAx44 +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 +CPLD_Xilinx:XC95144PQ100 +CPLD_Xilinx:XC95144XL-TQ100 +CPLD_Xilinx:XC95144XL-TQ144 +CPLD_Xilinx:XC9536PC44 +CPLD_Xilinx:XC9572XL-TQ100 +CPLD_Xilinx:XC9572XL-VQ64 +CPLD_Xilinx:XCR3064-VQ100 +CPLD_Xilinx:XCR3064-VQ44 +CPLD_Xilinx:XCR3064XL-VQ100 +CPLD_Xilinx:XCR3064XL-VQ44 +CPLD_Xilinx:XCR3128-VQ100 +CPLD_Xilinx:XCR3128XL-VQ100 +CPLD_Xilinx:XCR3256-TQ144 +CPLD_Xilinx:XCR3256XL-TQ144 +CPU:CDP1802ACE +CPU:CDP1802ACEX +CPU:CDP1802BCE +CPU:CDP1802BCEX +CPU:P4080-BGA1295 +CPU:Z80CPU +CPU_NXP_6800:MC6800 +CPU_NXP_6800:MC6802 +CPU_NXP_6800:MC6809 +CPU_NXP_6800:MC6809E +CPU_NXP_6800:MC68A00 +CPU_NXP_6800:MC68A02 +CPU_NXP_6800:MC68A09 +CPU_NXP_6800:MC68A09E +CPU_NXP_6800:MC68B00 +CPU_NXP_6800:MC68B02 +CPU_NXP_6800:MC68B09 +CPU_NXP_6800:MC68B09E +CPU_NXP_68000:68000D +CPU_NXP_68000:68008D +CPU_NXP_68000:68010D +CPU_NXP_68000:MC68000FN +CPU_NXP_68000:MC68332 +CPU_NXP_IMX:MCIMX6D4AVT +CPU_NXP_IMX:MCIMX6D5EYM +CPU_NXP_IMX:MCIMX6D6AVT +CPU_NXP_IMX:MCIMX6D7CVT +CPU_NXP_IMX:MCIMX6DP4AVT +CPU_NXP_IMX:MCIMX6DP5EVT +CPU_NXP_IMX:MCIMX6DP5EYM +CPU_NXP_IMX:MCIMX6DP6AVT +CPU_NXP_IMX:MCIMX6DP7CVT +CPU_NXP_IMX:MCIMX6Q4AVT +CPU_NXP_IMX:MCIMX6Q5EYM +CPU_NXP_IMX:MCIMX6Q6AVT +CPU_NXP_IMX:MCIMX6Q7CVT +CPU_NXP_IMX:MCIMX6QP4AVT +CPU_NXP_IMX:MCIMX6QP5EVT +CPU_NXP_IMX:MCIMX6QP5EYM +CPU_NXP_IMX:MCIMX6QP6AVT +CPU_NXP_IMX:MCIMX6QP7CVT +CPU_PowerPC:MPC8641D +Device:Ammeter_AC +Device:Ammeter_DC +Device:Antenna +Device:Antenna_Chip +Device:Antenna_Dipole +Device:Antenna_Loop +Device:Antenna_Shield +Device:Battery +Device:Battery_Cell +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 +Device:C_Polarized_Small_Series_2C +Device:C_Polarized_Small_US +Device:C_Polarized_Small_US_Series_2C +Device:C_Polarized_US +Device:C_Polarized_US_Series_2C +Device:C_Small +Device:C_Trim +Device:C_Trim_Differential +Device:C_Trim_Small +Device:C_Variable +Device:CircuitBreaker_1P +Device:CircuitBreaker_1P_US +Device:CircuitBreaker_2P +Device:CircuitBreaker_2P_US +Device:CircuitBreaker_3P +Device:CircuitBreaker_3P_US +Device:Crystal +Device:Crystal_GND2 +Device:Crystal_GND23 +Device:Crystal_GND23_Small +Device:Crystal_GND24 +Device:Crystal_GND24_Small +Device:Crystal_GND2_Small +Device:Crystal_GND3 +Device:Crystal_GND3_Small +Device:Crystal_Small +Device:D +Device:DIAC +Device:DIAC_Filled +Device:D_45deg +Device:D_45deg_Filled +Device:D_AAK +Device:D_Bridge_+-AA +Device:D_Bridge_+A-A +Device:D_Bridge_+AA- +Device:D_Bridge_-A+A +Device:D_Bridge_-AA+ +Device:D_Capacitance +Device:D_Capacitance_Filled +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 +Device:D_Dual_CommonAnode_KAK +Device:D_Dual_CommonAnode_KAK_Parallel +Device:D_Dual_CommonAnode_KAK_Split +Device:D_Dual_CommonAnode_KKA +Device:D_Dual_CommonAnode_KKA_Parallel +Device:D_Dual_CommonAnode_KKA_Split +Device:D_Dual_CommonCathode_AAK +Device:D_Dual_CommonCathode_AAK_Parallel +Device:D_Dual_CommonCathode_AAK_Split +Device:D_Dual_CommonCathode_AKA +Device:D_Dual_CommonCathode_AKA_Parallel +Device:D_Dual_CommonCathode_AKA_Split +Device:D_Dual_CommonCathode_KAA +Device:D_Dual_CommonCathode_KAA_Parallel +Device:D_Dual_CommonCathode_KAA_Split +Device:D_Dual_Series_ACK +Device:D_Dual_Series_ACK_Parallel +Device:D_Dual_Series_ACK_Split +Device:D_Dual_Series_AKC +Device:D_Dual_Series_AKC_Parallel +Device:D_Dual_Series_AKC_Split +Device:D_Dual_Series_CAK +Device:D_Dual_Series_CAK_Parallel +Device:D_Dual_Series_CAK_Split +Device:D_Dual_Series_CKA +Device:D_Dual_Series_CKA_Parallel +Device:D_Dual_Series_CKA_Split +Device:D_Dual_Series_KAC +Device:D_Dual_Series_KAC_Parallel +Device:D_Dual_Series_KAC_Split +Device:D_Dual_Series_KCA +Device:D_Dual_Series_KCA_Parallel +Device:D_Dual_Series_KCA_Split +Device:D_Filled +Device:D_KAA +Device:D_KAK +Device:D_KKA +Device:D_Laser_1A3C +Device:D_Laser_1C2A +Device:D_Laser_Photo_MType +Device:D_Laser_Photo_NType +Device:D_Laser_Photo_PType +Device:D_Photo +Device:D_Photo_Filled +Device:D_Radiation +Device:D_Radiation_Filled +Device:D_Schottky +Device:D_Schottky_AAK +Device:D_Schottky_AKA +Device:D_Schottky_AKK +Device:D_Schottky_Dual_CommonAnode_AKK +Device:D_Schottky_Dual_CommonAnode_AKK_Parallel +Device:D_Schottky_Dual_CommonAnode_AKK_Split +Device:D_Schottky_Dual_CommonAnode_KAK +Device:D_Schottky_Dual_CommonAnode_KAK_Parallel +Device:D_Schottky_Dual_CommonAnode_KAK_Split +Device:D_Schottky_Dual_CommonAnode_KKA +Device:D_Schottky_Dual_CommonAnode_KKA_Parallel +Device:D_Schottky_Dual_CommonAnode_KKA_Split +Device:D_Schottky_Dual_CommonCathode_AAK +Device:D_Schottky_Dual_CommonCathode_AAK_Parallel +Device:D_Schottky_Dual_CommonCathode_AAK_Split +Device:D_Schottky_Dual_CommonCathode_AKA +Device:D_Schottky_Dual_CommonCathode_AKA_Parallel +Device:D_Schottky_Dual_CommonCathode_AKA_Split +Device:D_Schottky_Dual_CommonCathode_KAA +Device:D_Schottky_Dual_CommonCathode_KAA_Parallel +Device:D_Schottky_Dual_CommonCathode_KAA_Split +Device:D_Schottky_Dual_Series_ACK +Device:D_Schottky_Dual_Series_ACK_Parallel +Device:D_Schottky_Dual_Series_ACK_Split +Device:D_Schottky_Dual_Series_AKC +Device:D_Schottky_Dual_Series_AKC_Parallel +Device:D_Schottky_Dual_Series_AKC_Split +Device:D_Schottky_Dual_Series_CAK +Device:D_Schottky_Dual_Series_CAK_Parallel +Device:D_Schottky_Dual_Series_CAK_Split +Device:D_Schottky_Dual_Series_CKA +Device:D_Schottky_Dual_Series_CKA_Parallel +Device:D_Schottky_Dual_Series_CKA_Split +Device:D_Schottky_Dual_Series_KAC +Device:D_Schottky_Dual_Series_KAC_Parallel +Device:D_Schottky_Dual_Series_KAC_Split +Device:D_Schottky_Dual_Series_KCA +Device:D_Schottky_Dual_Series_KCA_Parallel +Device:D_Schottky_Dual_Series_KCA_Split +Device:D_Schottky_Filled +Device:D_Schottky_KAA +Device:D_Schottky_KAK +Device:D_Schottky_KKA +Device:D_Schottky_Small +Device:D_Schottky_Small_Filled +Device:D_Shockley +Device:D_SiPM +Device:D_Small +Device:D_Small_Filled +Device:D_TVS +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 +Device:D_Tunnel_Filled +Device:D_Unitunnel +Device:D_Unitunnel_Filled +Device:D_Zener +Device:D_Zener_Dual_CommonAnode_AKK +Device:D_Zener_Dual_CommonAnode_AKK_Parallel +Device:D_Zener_Dual_CommonAnode_AKK_Split +Device:D_Zener_Dual_CommonAnode_KAK +Device:D_Zener_Dual_CommonAnode_KAK_Parallel +Device:D_Zener_Dual_CommonAnode_KAK_Split +Device:D_Zener_Dual_CommonAnode_KKA +Device:D_Zener_Dual_CommonAnode_KKA_Parallel +Device:D_Zener_Dual_CommonAnode_KKA_Split +Device:D_Zener_Dual_CommonCathode_AAK +Device:D_Zener_Dual_CommonCathode_AAK_Parallel +Device:D_Zener_Dual_CommonCathode_AAK_Split +Device:D_Zener_Dual_CommonCathode_AKA +Device:D_Zener_Dual_CommonCathode_AKA_Parallel +Device:D_Zener_Dual_CommonCathode_AKA_Split +Device:D_Zener_Dual_CommonCathode_KAA +Device:D_Zener_Dual_CommonCathode_KAA_Parallel +Device:D_Zener_Dual_CommonCathode_KAA_Split +Device:D_Zener_Filled +Device:D_Zener_Small +Device:D_Zener_Small_Filled +Device:DelayLine +Device:Earphone +Device:ElectromagneticActor +Device:FerriteBead +Device:FerriteBead_Small +Device:Filter_EMI_C +Device:Filter_EMI_CLC +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 +Device:Fuse_Polarized +Device:Fuse_Polarized_Small +Device:Fuse_Small +Device:GDT_2Pin +Device:GDT_3Pin +Device:Galvanometer +Device:HallGenerator +Device:Heater +Device:L +Device:LED +Device:LED_45deg +Device:LED_45deg_Filled +Device:LED_ABGR +Device:LED_ABRG +Device:LED_AGBR +Device:LED_AGRB +Device:LED_ARBG +Device:LED_ARGB +Device:LED_BAGR +Device:LED_BARG +Device:LED_BGAR +Device:LED_BGKR +Device:LED_BGRA +Device:LED_BGRK +Device:LED_BKGR +Device:LED_BKRG +Device:LED_BRAG +Device:LED_BRGA +Device:LED_BRGK +Device:LED_BRKG +Device:LED_Dual_AAK +Device:LED_Dual_AAKK +Device:LED_Dual_AKA +Device:LED_Dual_AKAK +Device:LED_Dual_AKKA +Device:LED_Dual_Bidirectional +Device:LED_Dual_KAK +Device:LED_Dual_KAKA +Device:LED_Dual_KKA +Device:LED_Filled +Device:LED_GABR +Device:LED_GARB +Device:LED_GBAR +Device:LED_GBKR +Device:LED_GBRA +Device:LED_GBRK +Device:LED_GKBR +Device:LED_GKRB +Device:LED_GRAB +Device:LED_GRBA +Device:LED_GRBK +Device:LED_GRKB +Device:LED_KBGR +Device:LED_KBRG +Device:LED_KGBR +Device:LED_KGRB +Device:LED_KRBG +Device:LED_KRGB +Device:LED_Pad +Device:LED_RABG +Device:LED_RAGB +Device:LED_RBAG +Device:LED_RBGA +Device:LED_RBGK +Device:LED_RBKG +Device:LED_RGAB +Device:LED_RGB +Device:LED_RGBA +Device:LED_RGBK +Device:LED_RGB_EP +Device:LED_RGKB +Device:LED_RKBG +Device:LED_RKGB +Device:LED_Series +Device:LED_Series_Pad +Device:LED_Small +Device:LED_Small_Filled +Device:L_45deg +Device:L_Coupled +Device:L_Coupled_1243 +Device:L_Coupled_1324 +Device:L_Coupled_1342 +Device:L_Coupled_1423 +Device:L_Coupled_Small +Device:L_Coupled_Small_1243 +Device:L_Coupled_Small_1324 +Device:L_Coupled_Small_1342 +Device:L_Coupled_Small_1423 +Device:L_Ferrite +Device:L_Ferrite_Coupled +Device:L_Ferrite_Coupled_1243 +Device:L_Ferrite_Coupled_1324 +Device:L_Ferrite_Coupled_1342 +Device:L_Ferrite_Coupled_1423 +Device:L_Ferrite_Coupled_Small +Device:L_Ferrite_Coupled_Small_1243 +Device:L_Ferrite_Coupled_Small_1324 +Device:L_Ferrite_Coupled_Small_1342 +Device:L_Ferrite_Coupled_Small_1423 +Device:L_Ferrite_Small +Device:L_Iron +Device:L_Iron_Coupled +Device:L_Iron_Coupled_1243 +Device:L_Iron_Coupled_1324 +Device:L_Iron_Coupled_1342 +Device:L_Iron_Coupled_1423 +Device:L_Iron_Coupled_Small +Device:L_Iron_Coupled_Small_1243 +Device:L_Iron_Coupled_Small_1324 +Device:L_Iron_Coupled_Small_1342 +Device:L_Iron_Coupled_Small_1423 +Device:L_Iron_Small +Device:L_Pack04 +Device:L_Small +Device:L_Trim +Device:Lamp +Device:Lamp_Flash +Device:Lamp_Neon +Device:Memristor +Device:Microphone +Device:Microphone_Condenser +Device:Microphone_Crystal +Device:Microphone_Ultrasound +Device:NetTie_2 +Device:NetTie_3 +Device:NetTie_3_Tee +Device:NetTie_4 +Device:NetTie_4_Cross +Device:Ohmmeter +Device:Opamp_Dual +Device:Opamp_Quad +Device:Oscilloscope +Device:PeltierElement +Device:Polyfuse +Device:Polyfuse_Small +Device:Q_NIGBT_CEG +Device:Q_NIGBT_CGE +Device:Q_NIGBT_ECG +Device:Q_NIGBT_ECGC +Device:Q_NIGBT_EGC +Device:Q_NIGBT_GCE +Device:Q_NIGBT_GCEC +Device:Q_NIGBT_GEC +Device:Q_NJFET_DGS +Device:Q_NJFET_DSG +Device:Q_NJFET_GDS +Device:Q_NJFET_GSD +Device:Q_NJFET_SDG +Device:Q_NJFET_SGD +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 +Device:Q_PJFET_GDS +Device:Q_PJFET_GSD +Device:Q_PJFET_SDG +Device:Q_PJFET_SGD +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 +Device:Q_Photo_NPN_CE +Device:Q_Photo_NPN_EBC +Device:Q_Photo_NPN_EC +Device:Q_SCR_AGK +Device:Q_SCR_AKG +Device:Q_SCR_GAK +Device:Q_SCR_GKA +Device:Q_SCR_KAG +Device:Q_SCR_KGA +Device:Q_Triac +Device:R +Device:RFShield_OnePiece +Device:RFShield_TwoPieces +Device:R_45deg +Device:R_Network03 +Device:R_Network03_Split +Device:R_Network03_US +Device:R_Network04 +Device:R_Network04_Split +Device:R_Network04_US +Device:R_Network05 +Device:R_Network05_Split +Device:R_Network05_US +Device:R_Network06 +Device:R_Network06_Split +Device:R_Network06_US +Device:R_Network07 +Device:R_Network07_Split +Device:R_Network07_US +Device:R_Network08 +Device:R_Network08_Split +Device:R_Network08_US +Device:R_Network09 +Device:R_Network09_Split +Device:R_Network09_US +Device:R_Network10 +Device:R_Network10_Split +Device:R_Network10_US +Device:R_Network11 +Device:R_Network11_Split +Device:R_Network11_US +Device:R_Network12 +Device:R_Network12_Split +Device:R_Network12_US +Device:R_Network13 +Device:R_Network13_Split +Device:R_Network13_US +Device:R_Network_Dividers_x02_SIP +Device:R_Network_Dividers_x03_SIP +Device:R_Network_Dividers_x04_SIP +Device:R_Network_Dividers_x05_SIP +Device:R_Network_Dividers_x06_SIP +Device:R_Network_Dividers_x07_SIP +Device:R_Network_Dividers_x08_SIP +Device:R_Network_Dividers_x09_SIP +Device:R_Network_Dividers_x10_SIP +Device:R_Network_Dividers_x11_SIP +Device:R_Pack02 +Device:R_Pack02_SIP +Device:R_Pack02_SIP_Split +Device:R_Pack02_Split +Device:R_Pack03 +Device:R_Pack03_SIP +Device:R_Pack03_SIP_Split +Device:R_Pack03_Split +Device:R_Pack04 +Device:R_Pack04_SIP +Device:R_Pack04_SIP_Split +Device:R_Pack04_Split +Device:R_Pack05 +Device:R_Pack05_SIP +Device:R_Pack05_SIP_Split +Device:R_Pack05_Split +Device:R_Pack06 +Device:R_Pack06_SIP +Device:R_Pack06_SIP_Split +Device:R_Pack06_Split +Device:R_Pack07 +Device:R_Pack07_SIP +Device:R_Pack07_SIP_Split +Device:R_Pack07_Split +Device:R_Pack08 +Device:R_Pack08_Split +Device:R_Pack09 +Device:R_Pack09_Split +Device:R_Pack10 +Device:R_Pack10_Split +Device:R_Pack11 +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 +Device:R_Potentiometer_Trim +Device:R_Potentiometer_Trim_US +Device:R_Potentiometer_US +Device:R_Shunt +Device:R_Shunt_US +Device:R_Small +Device:R_Small_US +Device:R_Trim +Device:R_US +Device:R_Variable +Device:R_Variable_US +Device:Resonator +Device:Resonator_Small +Device:RotaryEncoder +Device:RotaryEncoder_Switch +Device:RotaryEncoder_Switch_MP +Device:Solar_Cell +Device:Solar_Cells +Device:SparkGap +Device:Speaker +Device:Speaker_Crystal +Device:Speaker_Ultrasound +Device:Thermistor +Device:Thermistor_NTC +Device:Thermistor_NTC_3Wire +Device:Thermistor_NTC_4Wire +Device:Thermistor_NTC_US +Device:Thermistor_PTC +Device:Thermistor_PTC_3Wire +Device:Thermistor_PTC_4Wire +Device:Thermistor_PTC_US +Device:Thermistor_US +Device:Thermocouple +Device:Thermocouple_Alt +Device:Thermocouple_Block +Device:Transformer_1P_1S +Device:Transformer_1P_1S_SO8 +Device:Transformer_1P_2S +Device:Transformer_1P_SS +Device:Transformer_Audio +Device:Transformer_SP_1S +Device:Transformer_SP_2S +Device:Varistor +Device:Varistor_US +Device:VoltageDivider +Device:VoltageDivider_CenterPin1 +Device:VoltageDivider_CenterPin3 +Device:Voltmeter_AC +Device:Voltmeter_DC +Diode:1.5KExxA +Diode:1.5KExxCA +Diode:1.5SMCxxA +Diode:1.5SMCxxCA +Diode:1N4001 +Diode:1N4002 +Diode:1N4003 +Diode:1N4004 +Diode:1N4005 +Diode:1N4006 +Diode:1N4007 +Diode:1N4148 +Diode:1N4148W +Diode:1N4148WS +Diode:1N4148WT +Diode:1N4149 +Diode:1N4151 +Diode:1N4448 +Diode:1N4448W +Diode:1N4448WS +Diode:1N4448WT +Diode:1N47xxA +Diode:1N4933 +Diode:1N4934 +Diode:1N4935 +Diode:1N4936 +Diode:1N4937 +Diode:1N53xxB +Diode:1N5400 +Diode:1N5401 +Diode:1N5402 +Diode:1N5403 +Diode:1N5404 +Diode:1N5405 +Diode:1N5406 +Diode:1N5407 +Diode:1N5408 +Diode:1N5711 +Diode:1N5711UR +Diode:1N5712 +Diode:1N5712UR +Diode:1N5817 +Diode:1N5818 +Diode:1N5819 +Diode:1N5819WS +Diode:1N5820 +Diode:1N5821 +Diode:1N5822 +Diode:1N5908 +Diode:1N6263 +Diode:1N62xxA +Diode:1N62xxCA +Diode:1N630xA +Diode:1N630xCA +Diode:1N6857 +Diode:1N6857UR +Diode:1N6858 +Diode:1N6858UR +Diode:1N914 +Diode:1N914WT +Diode:1SS355VM +Diode:2BZX84Cxx +Diode:5KPxxA +Diode:5KPxxCA +Diode:B120-E3 +Diode:B130-E3 +Diode:B140-E3 +Diode:B150-E3 +Diode:B160-E3 +Diode:B220 +Diode:B230 +Diode:B240 +Diode:B250 +Diode:B260 +Diode:B320 +Diode:B330 +Diode:B340 +Diode:B350 +Diode:B360 +Diode:BA157 +Diode:BA158 +Diode:BA159 +Diode:BA243 +Diode:BA244 +Diode:BA282 +Diode:BA283 +Diode:BAR42FILM +Diode:BAR43FILM +Diode:BAS16TW +Diode:BAS16VY +Diode:BAS16W +Diode:BAS19 +Diode:BAS20 +Diode:BAS21 +Diode:BAS316 +Diode:BAS40-04 +Diode:BAS516 +Diode:BAT160A +Diode:BAT160C +Diode:BAT160S +Diode:BAT41 +Diode:BAT42 +Diode:BAT42W-V +Diode:BAT43 +Diode:BAT43W-V +Diode:BAT46 +Diode:BAT48JFILM +Diode:BAT48RL +Diode:BAT48ZFILM +Diode:BAT54A +Diode:BAT54ADW +Diode:BAT54AW +Diode:BAT54C +Diode:BAT54CW +Diode:BAT54J +Diode:BAT54S +Diode:BAT54SDW +Diode:BAT54SW +Diode:BAT54W +Diode:BAT60A +Diode:BAT85 +Diode:BAT86 +Diode:BAT86S +Diode:BAV16W +Diode:BAV17 +Diode:BAV18 +Diode:BAV19 +Diode:BAV199DW +Diode:BAV20 +Diode:BAV21 +Diode:BAV300 +Diode:BAV301 +Diode:BAV302 +Diode:BAV303 +Diode:BAV70 +Diode:BAV70M +Diode:BAV70S +Diode:BAV70T +Diode:BAV70W +Diode:BAV756S +Diode:BAV99 +Diode:BAV99S +Diode:BAW56DW +Diode:BAW56S +Diode:BAW75 +Diode:BAW76 +Diode:BAY93 +Diode:BYV79-100 +Diode:BYV79-150 +Diode:BYV79-200 +Diode:BZD27Cxx +Diode:BZM55Bxx +Diode:BZM55Cxx +Diode:BZT52Bxx +Diode:BZV55B10 +Diode:BZV55B11 +Diode:BZV55B12 +Diode:BZV55B13 +Diode:BZV55B15 +Diode:BZV55B16 +Diode:BZV55B18 +Diode:BZV55B20 +Diode:BZV55B22 +Diode:BZV55B24 +Diode:BZV55B27 +Diode:BZV55B2V4 +Diode:BZV55B2V7 +Diode:BZV55B30 +Diode:BZV55B33 +Diode:BZV55B36 +Diode:BZV55B39 +Diode:BZV55B3V0 +Diode:BZV55B3V3 +Diode:BZV55B3V6 +Diode:BZV55B3V9 +Diode:BZV55B43 +Diode:BZV55B47 +Diode:BZV55B4V3 +Diode:BZV55B4V7 +Diode:BZV55B51 +Diode:BZV55B56 +Diode:BZV55B5V1 +Diode:BZV55B5V6 +Diode:BZV55B62 +Diode:BZV55B68 +Diode:BZV55B6V2 +Diode:BZV55B6V8 +Diode:BZV55B75 +Diode:BZV55B7V5 +Diode:BZV55B8V2 +Diode:BZV55B9V1 +Diode:BZV55C10 +Diode:BZV55C11 +Diode:BZV55C12 +Diode:BZV55C13 +Diode:BZV55C15 +Diode:BZV55C16 +Diode:BZV55C18 +Diode:BZV55C20 +Diode:BZV55C22 +Diode:BZV55C24 +Diode:BZV55C27 +Diode:BZV55C2V4 +Diode:BZV55C2V7 +Diode:BZV55C30 +Diode:BZV55C33 +Diode:BZV55C36 +Diode:BZV55C39 +Diode:BZV55C3V0 +Diode:BZV55C3V3 +Diode:BZV55C3V6 +Diode:BZV55C3V9 +Diode:BZV55C43 +Diode:BZV55C47 +Diode:BZV55C4V3 +Diode:BZV55C4V7 +Diode:BZV55C51 +Diode:BZV55C56 +Diode:BZV55C5V1 +Diode:BZV55C5V6 +Diode:BZV55C62 +Diode:BZV55C68 +Diode:BZV55C6V2 +Diode:BZV55C6V8 +Diode:BZV55C75 +Diode:BZV55C7V5 +Diode:BZV55C8V2 +Diode:BZV55C9V1 +Diode:BZX384xxxx +Diode:BZX84Cxx +Diode:C3D02060A +Diode:C3D02060E +Diode:C3D02060F +Diode:C3D02065E +Diode:C3D03060A +Diode:C3D03060E +Diode:C3D03060F +Diode:C3D03065E +Diode:C3D04060A +Diode:C3D04060E +Diode:C3D04060F +Diode:C3D04065A +Diode:C3D04065E +Diode:C3D06060A +Diode:C3D06060F +Diode:C3D06060G +Diode:C3D06065A +Diode:C3D06065E +Diode:C3D06065I +Diode:C3D08060A +Diode:C3D08060G +Diode:C3D08065A +Diode:C3D08065E +Diode:C3D08065I +Diode:C3D10060A +Diode:C3D10060G +Diode:C3D10065A +Diode:C3D10065E +Diode:C3D10065I +Diode:C3D10170H +Diode:C3D12065A +Diode:C3D16060D +Diode:C3D16065A +Diode:C3D16065D +Diode:C3D1P7060Q +Diode:C3D20060D +Diode:C3D20065D +Diode:C3D25170H +Diode:C3D30065D +Diode:C4D02120A +Diode:C4D02120E +Diode:C4D05120A +Diode:C4D05120E +Diode:C4D08120A +Diode:C4D08120E +Diode:C4D10120A +Diode:C4D10120D +Diode:C4D10120E +Diode:C4D10120H +Diode:C4D15120A +Diode:C4D15120D +Diode:C4D15120H +Diode:C4D20120A +Diode:C4D20120D +Diode:C4D20120H +Diode:C4D30120D +Diode:C4D40120D +Diode:C5D50065D +Diode:CD4148W +Diode:CDBA3100-HF +Diode:CDBA340-HF +Diode:CDBA360-HF +Diode:CDBU40-HF +Diode:CSD01060A +Diode:CSD01060E +Diode:CVFD20065A +Diode:Central_Semi_CMKD4448 +Diode:Central_Semi_CMKD6001 +Diode:Comchip_ACDSV6-4448TI-G +Diode:Comchip_CDSV6-4148-G +Diode:Comchip_CDSV6-4448TI-G +Diode:DB3 +Diode:DB4 +Diode:DC34 +Diode:DSB2810 +Diode:DSB5712 +Diode:DZ2S030X0L +Diode:DZ2S033X0L +Diode:DZ2S036X0L +Diode:DZ2S039X0L +Diode:DZ2S047X0L +Diode:DZ2S051X0L +Diode:DZ2S056X0L +Diode:DZ2S068X0L +Diode:DZ2S082X0L +Diode:DZ2S100X0L +Diode:DZ2S110X0L +Diode:DZ2S120X0L +Diode:DZ2S130X0L +Diode:DZ2S150X0L +Diode:DZ2S160X0L +Diode:DZ2S180X0L +Diode:DZ2S200X0L +Diode:DZ2S360X0L +Diode:ESD131-B1-W0201 +Diode:ESD5Zxx +Diode:ESD9B3.3ST5G +Diode:ESD9B5.0ST5G +Diode:ESH2PB +Diode:ESH2PC +Diode:ESH2PD +Diode:HN2D02FU +Diode:IDDD04G65C6 +Diode:IDDD06G65C6 +Diode:IDDD08G65C6 +Diode:IDDD10G65C6 +Diode:IDDD12G65C6 +Diode:IDDD16G65C6 +Diode:IDDD20G65C6 +Diode:LL41 +Diode:LL4148 +Diode:LL42 +Diode:LL43 +Diode:LL4448 +Diode:MBR0520 +Diode:MBR0520LT +Diode:MBR0530 +Diode:MBR0540 +Diode:MBR0550 +Diode:MBR0560 +Diode:MBR0570 +Diode:MBR0580 +Diode:MBR1020VL +Diode:MBR340 +Diode:MBR735 +Diode:MBR745 +Diode:MBRA340 +Diode:MBRS340 +Diode:MCL4148 +Diode:MCL4448 +Diode:MM3Zxx +Diode:MM5Zxx +Diode:MMBD4148TW +Diode:MMBD4448HADW +Diode:MMBD4448HCQW +Diode:MMBD4448HTW +Diode:MMBZxx +Diode:MMSD4148 +Diode:MMSD914 +Diode:MRA4003T3G +Diode:MRA4004T3G +Diode:MRA4005T3G +Diode:MRA4006T3G +Diode:MRA4007T3G +Diode:NRVA4003T3G +Diode:NRVA4004T3G +Diode:NRVA4005T3G +Diode:NRVA4006T3G +Diode:NRVA4007T3G +Diode:NSR0340HT1G +Diode:PMEG030V030EPD +Diode:PMEG030V050EPD +Diode:PMEG040V030EPD +Diode:PMEG040V050EPD +Diode:PMEG045T050EPD +Diode:PMEG045T100EPD +Diode:PMEG045T150EIPD +Diode:PMEG045T150EPD +Diode:PMEG045V050EPD +Diode:PMEG045V100EPD +Diode:PMEG045V150EPD +Diode:PMEG050T150EPD +Diode:PMEG050V030EPD +Diode:PMEG050V150EPD +Diode:PMEG060V030EPD +Diode:PMEG060V050EPD +Diode:PMEG060V100EPD +Diode:PMEG10010ELR +Diode:PMEG10020AELR +Diode:PMEG10020ELR +Diode:PMEG100V060ELPD +Diode:PMEG100V080ELPD +Diode:PMEG100V100ELPD +Diode:PMEG1020EH +Diode:PMEG1020EJ +Diode:PMEG1030EH +Diode:PMEG1030EJ +Diode:PMEG2005EH +Diode:PMEG2005EJ +Diode:PMEG2010AEH +Diode:PMEG2010AEJ +Diode:PMEG2010AET +Diode:PMEG2010BER +Diode:PMEG2010EH +Diode:PMEG2010EJ +Diode:PMEG2010ER +Diode:PMEG2010ET +Diode:PMEG2015EH +Diode:PMEG2015EJ +Diode:PMEG2020EH +Diode:PMEG2020EJ +Diode:PMEG3002EJ +Diode:PMEG3005EH +Diode:PMEG3005EJ +Diode:PMEG3010BER +Diode:PMEG3010CEH +Diode:PMEG3010CEJ +Diode:PMEG3010EH +Diode:PMEG3010EJ +Diode:PMEG3010ER +Diode:PMEG3010ET +Diode:PMEG3015EH +Diode:PMEG3015EJ +Diode:PMEG3020BER +Diode:PMEG3020EH +Diode:PMEG3020EJ +Diode:PMEG3020ER +Diode:PMEG4002EJ +Diode:PMEG4005CEJ +Diode:PMEG4005EH +Diode:PMEG4005EJ +Diode:PMEG4010CEH +Diode:PMEG4010CEJ +Diode:PMEG4010EH +Diode:PMEG4010EJ +Diode:PMEG4010ER +Diode:PMEG4010ET +Diode:PMEG4020ER +Diode:PMEG4030ER +Diode:PMEG4050EP +Diode:PMEG40T10ER +Diode:PMEG40T20ER +Diode:PMEG40T30ER +Diode:PMEG45A10EPD +Diode:PMEG45T15EPD +Diode:PMEG45U10EPD +Diode:PMEG6002EJ +Diode:PMEG6010CEH +Diode:PMEG6010CEJ +Diode:PMEG6010ELR +Diode:PMEG6010ER +Diode:PMEG6020AELR +Diode:PMEG6020ELR +Diode:PMEG6020ER +Diode:PMEG6030EP +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 +Diode:Rohm_UMP11N +Diode:S2JTR +Diode:SB120 +Diode:SB130 +Diode:SB140 +Diode:SB150 +Diode:SB160 +Diode:SB5H100 +Diode:SB5H90 +Diode:SD05_SOD323 +Diode:SD103ATW +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 +Diode:SM4003 +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 +Diode:SM6T150A +Diode:SM6T15A +Diode:SM6T18A +Diode:SM6T200A +Diode:SM6T220A +Diode:SM6T22A +Diode:SM6T24A +Diode:SM6T27A +Diode:SM6T30A +Diode:SM6T33A +Diode:SM6T36A +Diode:SM6T39A +Diode:SM6T56A +Diode:SM6T68A +Diode:SM6T6V8A +Diode:SM6T75A +Diode:SM6T7V5A +Diode:SM712_SOT23 +Diode:SMAJ100A +Diode:SMAJ100CA +Diode:SMAJ10A +Diode:SMAJ10CA +Diode:SMAJ110A +Diode:SMAJ110CA +Diode:SMAJ11A +Diode:SMAJ11CA +Diode:SMAJ120A +Diode:SMAJ120CA +Diode:SMAJ12A +Diode:SMAJ12CA +Diode:SMAJ130A +Diode:SMAJ130CA +Diode:SMAJ13A +Diode:SMAJ13CA +Diode:SMAJ14A +Diode:SMAJ14CA +Diode:SMAJ150A +Diode:SMAJ150CA +Diode:SMAJ15A +Diode:SMAJ15CA +Diode:SMAJ160A +Diode:SMAJ160CA +Diode:SMAJ16A +Diode:SMAJ16CA +Diode:SMAJ170A +Diode:SMAJ170CA +Diode:SMAJ17A +Diode:SMAJ17CA +Diode:SMAJ180A +Diode:SMAJ180CA +Diode:SMAJ188A +Diode:SMAJ188CA +Diode:SMAJ18A +Diode:SMAJ18CA +Diode:SMAJ200A +Diode:SMAJ200CA +Diode:SMAJ20A +Diode:SMAJ20CA +Diode:SMAJ220A +Diode:SMAJ220CA +Diode:SMAJ22A +Diode:SMAJ22CA +Diode:SMAJ24A +Diode:SMAJ24CA +Diode:SMAJ250A +Diode:SMAJ250CA +Diode:SMAJ26A +Diode:SMAJ26CA +Diode:SMAJ28A +Diode:SMAJ28CA +Diode:SMAJ300A +Diode:SMAJ300CA +Diode:SMAJ30A +Diode:SMAJ30CA +Diode:SMAJ33A +Diode:SMAJ33CA +Diode:SMAJ350A +Diode:SMAJ350CA +Diode:SMAJ36A +Diode:SMAJ36CA +Diode:SMAJ400A +Diode:SMAJ400CA +Diode:SMAJ40A +Diode:SMAJ40CA +Diode:SMAJ43A +Diode:SMAJ43CA +Diode:SMAJ440A +Diode:SMAJ440CA +Diode:SMAJ45A +Diode:SMAJ45CA +Diode:SMAJ48A +Diode:SMAJ48CA +Diode:SMAJ5.0A +Diode:SMAJ5.0CA +Diode:SMAJ51A +Diode:SMAJ51CA +Diode:SMAJ54A +Diode:SMAJ54CA +Diode:SMAJ58A +Diode:SMAJ58CA +Diode:SMAJ6.0A +Diode:SMAJ6.0CA +Diode:SMAJ6.5A +Diode:SMAJ6.5CA +Diode:SMAJ60A +Diode:SMAJ60CA +Diode:SMAJ64A +Diode:SMAJ64CA +Diode:SMAJ7.0A +Diode:SMAJ7.0CA +Diode:SMAJ7.5A +Diode:SMAJ7.5CA +Diode:SMAJ70A +Diode:SMAJ70CA +Diode:SMAJ75A +Diode:SMAJ75CA +Diode:SMAJ78A +Diode:SMAJ78CA +Diode:SMAJ8.0A +Diode:SMAJ8.0CA +Diode:SMAJ8.5A +Diode:SMAJ8.5CA +Diode:SMAJ85A +Diode:SMAJ85CA +Diode:SMAJ9.0A +Diode:SMAJ9.0CA +Diode:SMAJ90A +Diode:SMAJ90CA +Diode:SMF10A +Diode:SMF11A +Diode:SMF12A +Diode:SMF13A +Diode:SMF14A +Diode:SMF15A +Diode:SMF16A +Diode:SMF17A +Diode:SMF18A +Diode:SMF20A +Diode:SMF22A +Diode:SMF24A +Diode:SMF26A +Diode:SMF28A +Diode:SMF30A +Diode:SMF33A +Diode:SMF36A +Diode:SMF40A +Diode:SMF43A +Diode:SMF45A +Diode:SMF48A +Diode:SMF51A +Diode:SMF54A +Diode:SMF58A +Diode:SMF5V0A +Diode:SMF6V0A +Diode:SMF6V5A +Diode:SMF7V0A +Diode:SMF7V5A +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 +Diode:STBR6012WY +Diode:STTH2002D +Diode:STTH2002G +Diode:STTH212S +Diode:STTH212U +Diode:SZESD9B5.0ST5G +Diode:Toshiba_HN1D01FU +Diode:UF5400 +Diode:UF5401 +Diode:UF5402 +Diode:UF5403 +Diode:UF5404 +Diode:UF5405 +Diode:UF5406 +Diode:UF5407 +Diode:UF5408 +Diode:US1A +Diode:US1B +Diode:US1D +Diode:US1G +Diode:US1J +Diode:US1K +Diode:US1M +Diode:US2AA +Diode:US2BA +Diode:US2DA +Diode:US2FA +Diode:US2GA +Diode:US2JA +Diode:US2KA +Diode:US2MA +Diode:VS-6ESU06 +Diode:Z1SMAxxx +Diode:Z2SMBxxx +Diode:Z3SMCxxx +Diode:ZMDxx +Diode:ZMMxx +Diode:ZMYxx +Diode:ZPDxx +Diode:ZPYxx +Diode:ZYxxx +Diode_Bridge:ABS10 +Diode_Bridge:ABS2 +Diode_Bridge:ABS4 +Diode_Bridge:ABS6 +Diode_Bridge:ABS8 +Diode_Bridge:B125C1500G +Diode_Bridge:B125C2300-1500A +Diode_Bridge:B125C2300-1500B +Diode_Bridge:B125C3x00-2200A +Diode_Bridge:B125C5000-3x00A +Diode_Bridge:B125C800DM +Diode_Bridge:B125R +Diode_Bridge:B250C1500G +Diode_Bridge:B250C2300-1500A +Diode_Bridge:B250C2300-1500B +Diode_Bridge:B250C3x00-2200A +Diode_Bridge:B250C5000-3x00A +Diode_Bridge:B250C800DM +Diode_Bridge:B250R +Diode_Bridge:B380C1500G +Diode_Bridge:B380C2300-1500A +Diode_Bridge:B380C2300-1500B +Diode_Bridge:B380C3x00-2200A +Diode_Bridge:B380C5000-3x00A +Diode_Bridge:B380C800DM +Diode_Bridge:B380R +Diode_Bridge:B40C1500G +Diode_Bridge:B40C2300-1500A +Diode_Bridge:B40C2300-1500B +Diode_Bridge:B40C3x00-2200A +Diode_Bridge:B40C5000-3x00A +Diode_Bridge:B40C800DM +Diode_Bridge:B40R +Diode_Bridge:B500C2300-1500A +Diode_Bridge:B500C3x00-2200A +Diode_Bridge:B500C5000-3x00A +Diode_Bridge:B500R +Diode_Bridge:B700C2300-1500B +Diode_Bridge:B80C1500G +Diode_Bridge:B80C2300-1500A +Diode_Bridge:B80C2300-1500B +Diode_Bridge:B80C3x00-2200A +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 +Diode_Bridge:GBU4B +Diode_Bridge:GBU4D +Diode_Bridge:GBU4G +Diode_Bridge:GBU4J +Diode_Bridge:GBU4K +Diode_Bridge:GBU4M +Diode_Bridge:GBU6A +Diode_Bridge:GBU6B +Diode_Bridge:GBU6D +Diode_Bridge:GBU6G +Diode_Bridge:GBU6J +Diode_Bridge:GBU6K +Diode_Bridge:GBU6M +Diode_Bridge:GBU8A +Diode_Bridge:GBU8B +Diode_Bridge:GBU8D +Diode_Bridge:GBU8G +Diode_Bridge:GBU8J +Diode_Bridge:GBU8K +Diode_Bridge:GBU8M +Diode_Bridge:GUO40-08NO1 +Diode_Bridge:GUO40-12NO1 +Diode_Bridge:GUO40-16NO1 +Diode_Bridge:KBPC15005T +Diode_Bridge:KBPC15005W +Diode_Bridge:KBPC1501T +Diode_Bridge:KBPC1501W +Diode_Bridge:KBPC1502T +Diode_Bridge:KBPC1502W +Diode_Bridge:KBPC1504T +Diode_Bridge:KBPC1504W +Diode_Bridge:KBPC1506T +Diode_Bridge:KBPC1506W +Diode_Bridge:KBPC1508T +Diode_Bridge:KBPC1508W +Diode_Bridge:KBPC1510T +Diode_Bridge:KBPC1510W +Diode_Bridge:KBPC25005T +Diode_Bridge:KBPC25005W +Diode_Bridge:KBPC2501T +Diode_Bridge:KBPC2501W +Diode_Bridge:KBPC2502T +Diode_Bridge:KBPC2502W +Diode_Bridge:KBPC2504T +Diode_Bridge:KBPC2504W +Diode_Bridge:KBPC2506T +Diode_Bridge:KBPC2506W +Diode_Bridge:KBPC2508T +Diode_Bridge:KBPC2508W +Diode_Bridge:KBPC2510T +Diode_Bridge:KBPC2510W +Diode_Bridge:KBPC35005T +Diode_Bridge:KBPC35005W +Diode_Bridge:KBPC3501T +Diode_Bridge:KBPC3501W +Diode_Bridge:KBPC3502T +Diode_Bridge:KBPC3502W +Diode_Bridge:KBPC3504T +Diode_Bridge:KBPC3504W +Diode_Bridge:KBPC3506T +Diode_Bridge:KBPC3506W +Diode_Bridge:KBPC3508T +Diode_Bridge:KBPC3508W +Diode_Bridge:KBPC3510T +Diode_Bridge:KBPC3510W +Diode_Bridge:KBPC50005T +Diode_Bridge:KBPC50005W +Diode_Bridge:KBPC5001T +Diode_Bridge:KBPC5001W +Diode_Bridge:KBPC5002T +Diode_Bridge:KBPC5002W +Diode_Bridge:KBPC5004T +Diode_Bridge:KBPC5004W +Diode_Bridge:KBPC5006T +Diode_Bridge:KBPC5006W +Diode_Bridge:KBPC5008T +Diode_Bridge:KBPC5008W +Diode_Bridge:KBPC5010T +Diode_Bridge:KBPC5010W +Diode_Bridge:KBU4A +Diode_Bridge:KBU4B +Diode_Bridge:KBU4D +Diode_Bridge:KBU4G +Diode_Bridge:KBU4J +Diode_Bridge:KBU4K +Diode_Bridge:KBU4M +Diode_Bridge:KBU6A +Diode_Bridge:KBU6B +Diode_Bridge:KBU6D +Diode_Bridge:KBU6G +Diode_Bridge:KBU6J +Diode_Bridge:KBU6K +Diode_Bridge:KBU6M +Diode_Bridge:KBU8A +Diode_Bridge:KBU8B +Diode_Bridge:KBU8D +Diode_Bridge:KBU8G +Diode_Bridge:KBU8J +Diode_Bridge:KBU8K +Diode_Bridge:KBU8M +Diode_Bridge:MB2S +Diode_Bridge:MB4S +Diode_Bridge:MB6S +Diode_Bridge:MBL104S +Diode_Bridge:MBL106S +Diode_Bridge:MBL108S +Diode_Bridge:MBL110S +Diode_Bridge:MDB10S +Diode_Bridge:MDB6S +Diode_Bridge:MDB8S +Diode_Bridge:RB151 +Diode_Bridge:RB152 +Diode_Bridge:RB153 +Diode_Bridge:RB154 +Diode_Bridge:RB155 +Diode_Bridge:RB156 +Diode_Bridge:RB157 +Diode_Bridge:RMB2S +Diode_Bridge:RMB4S +Diode_Bridge:SC35VB160S-G +Diode_Bridge:SC35VB80S-G +Diode_Bridge:VS-KBPC1005 +Diode_Bridge:VS-KBPC101 +Diode_Bridge:VS-KBPC102 +Diode_Bridge:VS-KBPC104 +Diode_Bridge:VS-KBPC106 +Diode_Bridge:VS-KBPC108 +Diode_Bridge:VS-KBPC110 +Diode_Bridge:VS-KBPC6005 +Diode_Bridge:VS-KBPC601 +Diode_Bridge:VS-KBPC602 +Diode_Bridge:VS-KBPC604 +Diode_Bridge:VS-KBPC606 +Diode_Bridge:VS-KBPC608 +Diode_Bridge:VS-KBPC610 +Diode_Bridge:VS-KBPC8005 +Diode_Bridge:VS-KBPC801 +Diode_Bridge:VS-KBPC802 +Diode_Bridge:VS-KBPC804 +Diode_Bridge:VS-KBPC806 +Diode_Bridge:VS-KBPC808 +Diode_Bridge:VS-KBPC810 +Diode_Bridge:W005G +Diode_Bridge:W01G +Diode_Bridge:W02G +Diode_Bridge:W04G +Diode_Bridge:W06G +Diode_Bridge:W08G +Diode_Bridge:W10G +Diode_Laser:PL520 +Diode_Laser:PLT5_450B +Diode_Laser:PLT5_488 +Diode_Laser:PLT5_510 +Diode_Laser:SPL_PL90 +Display_Character:AD-121F2 +Display_Character:CA56-12CGKWA +Display_Character:CA56-12EWA +Display_Character:CA56-12SEKWA +Display_Character:CA56-12SRWA +Display_Character:CA56-12SURKWA +Display_Character:CA56-12SYKWA +Display_Character:CC56-12CGKWA +Display_Character:CC56-12EWA +Display_Character:CC56-12GWA +Display_Character:CC56-12SEKWA +Display_Character:CC56-12SRWA +Display_Character:CC56-12SURKWA +Display_Character:CC56-12SYKWA +Display_Character:CC56-12YWA +Display_Character:D148K +Display_Character:D168K +Display_Character:D198K +Display_Character:D1X8K-14BL +Display_Character:DA04-11CGKWA +Display_Character:DA04-11EWA +Display_Character:DA04-11GWA +Display_Character:DA04-11SEKWA +Display_Character:DA04-11SRWA +Display_Character:DA04-11SURKWA +Display_Character:DA04-11SYKWA +Display_Character:DA56-11CGKWA +Display_Character:DA56-11EWA +Display_Character:DA56-11GWA +Display_Character:DA56-11SEKWA +Display_Character:DA56-11SRWA +Display_Character:DA56-11SURKWA +Display_Character:DA56-11SYKWA +Display_Character:DA56-11YWA +Display_Character:DC56-11CGKWA +Display_Character:DC56-11EWA +Display_Character:DC56-11GWA +Display_Character:DC56-11SEKWA +Display_Character:DC56-11SRWA +Display_Character:DC56-11SURKWA +Display_Character:DC56-11SYKWA +Display_Character:DC56-11YWA +Display_Character:DE113-XX-XX +Display_Character:DE114-RS-20 +Display_Character:DE122-XX-XX +Display_Character:DE170-XX-XX +Display_Character:EA_T123X-I2C +Display_Character:ELD-426SYGWA +Display_Character:HDSM-441B +Display_Character:HDSM-443B +Display_Character:HDSM-541B +Display_Character:HDSM-543B +Display_Character:HDSP-7401 +Display_Character:HDSP-7403 +Display_Character:HDSP-7501 +Display_Character:HDSP-7503 +Display_Character:HDSP-7507 +Display_Character:HDSP-7508 +Display_Character:HDSP-7801 +Display_Character:HDSP-7803 +Display_Character:HDSP-7807 +Display_Character:HDSP-7808 +Display_Character:HDSP-A151 +Display_Character:HDSP-A153 +Display_Character:HDSP-A401 +Display_Character:HDSP-A403 +Display_Character:HY1602E +Display_Character:KCSA02-105 +Display_Character:KCSA02-106 +Display_Character:KCSA02-107 +Display_Character:KCSA02-123 +Display_Character:KCSA02-136 +Display_Character:KCSC02-105 +Display_Character:KCSC02-106 +Display_Character:KCSC02-107 +Display_Character:KCSC02-123 +Display_Character:KCSC02-136 +Display_Character:LCD-016N002L +Display_Character:LM16255K +Display_Character:LTC-4627JD +Display_Character:LTC-4627JD-01 +Display_Character:LTC-4627JF +Display_Character:LTC-4627JG +Display_Character:LTC-4627JR +Display_Character:LTC-4627JS +Display_Character:LTS-6960HR +Display_Character:LTS-6980HR +Display_Character:MAN3410A +Display_Character:MAN3420A +Display_Character:MAN3440A +Display_Character:MAN3610A +Display_Character:MAN3620A +Display_Character:MAN3630A +Display_Character:MAN3640A +Display_Character:MAN3810A +Display_Character:MAN3820A +Display_Character:MAN3840A +Display_Character:MAN71A +Display_Character:MAN72A +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 +Display_Character:SA15-11GWA +Display_Character:SA15-11SRWA +Display_Character:SA39-11EWA +Display_Character:SA39-11GWA +Display_Character:SA39-11SRWA +Display_Character:SA39-11YWA +Display_Character:SA39-12EWA +Display_Character:SA39-12GWA +Display_Character:SA39-12SRWA +Display_Character:SA39-12YWA +Display_Character:SBC18-11EGWA +Display_Character:SBC18-11SURKCGKWA +Display_Character:SC39-11EWA +Display_Character:SC39-11GWA +Display_Character:SC39-11SRWA +Display_Character:SC39-11YWA +Display_Character:SC39-12EWA +Display_Character:SC39-12GWA +Display_Character:SC39-12SRWA +Display_Character:SC39-12YWA +Display_Character:SM420561N +Display_Character:WC1602A +Display_Graphic:AG12864E +Display_Graphic:EA_DOGL128X-6 +Display_Graphic:EA_DOGM128X-6 +Display_Graphic:EA_DOGS104B-A +Display_Graphic:EA_DOGXL160-7 +Display_Graphic:EA_eDIP128B-6LW +Display_Graphic:EA_eDIP128B-6LWTP +Display_Graphic:EA_eDIP128W-6LW +Display_Graphic:EA_eDIP128W-6LWTP +Display_Graphic:EA_eDIP160B-7LW +Display_Graphic:EA_eDIP160B-7LWTP +Display_Graphic:EA_eDIP160W-7LW +Display_Graphic:EA_eDIP160W-7LWTP +Display_Graphic:EA_eDIP240B-7LW +Display_Graphic:EA_eDIP240B-7LWTP +Display_Graphic:EA_eDIP240J-7LA +Display_Graphic:EA_eDIP240J-7LATP +Display_Graphic:EA_eDIP240J-7LW +Display_Graphic:EA_eDIP240J-7LWTP +Display_Graphic:EA_eDIP320B-8LW +Display_Graphic:EA_eDIP320B-8LWTP +Display_Graphic:EA_eDIP320J-8LA +Display_Graphic:EA_eDIP320J-8LATP +Display_Graphic:EA_eDIP320J-8LW +Display_Graphic:EA_eDIP320J-8LWTP +Display_Graphic:EA_eDIPTFT32-A +Display_Graphic:EA_eDIPTFT32-ATP +Display_Graphic:EA_eDIPTFT43-A +Display_Graphic:EA_eDIPTFT43-ATC +Display_Graphic:EA_eDIPTFT43-ATP +Display_Graphic:EA_eDIPTFT43-ATS +Display_Graphic:EA_eDIPTFT57-A +Display_Graphic:EA_eDIPTFT57-ATP +Display_Graphic:EA_eDIPTFT70-A +Display_Graphic:EA_eDIPTFT70-ATC +Display_Graphic:EA_eDIPTFT70-ATP +Display_Graphic:ERM19264 +Display_Graphic:NHD-C12832A1Z-FSRGB +Display_Graphic:OLED-128O064D +Driver_Display:82720 +Driver_Display:ADS7843E +Driver_Display:ADS7843E-2K5 +Driver_Display:ADS7843EG4 +Driver_Display:ADS7843IDBQRQ1 +Driver_Display:AY0438X-L +Driver_Display:AY0438X-P +Driver_Display:CR2013-MI2120 +Driver_Display:XPT2046QF +Driver_Display:XPT2046TS +Driver_FET:1EDN7550B +Driver_FET:1EDN8550B +Driver_FET:2ED1323S12P +Driver_FET:2ED1324S12P +Driver_FET:2ED21824S06J +Driver_FET:2EDL23N06PJXUMA1 +Driver_FET:ACPL-336J +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 +Driver_FET:FAN7388 +Driver_FET:FAN7842 +Driver_FET:FAN7888 +Driver_FET:FL5150MX +Driver_FET:FL5160MX +Driver_FET:HCPL-3120 +Driver_FET:HCPL-314J +Driver_FET:HIP2100_DFN +Driver_FET:HIP2100_EPSOIC +Driver_FET:HIP2100_QFN +Driver_FET:HIP2100_SOIC +Driver_FET:HIP2101_DFN +Driver_FET:HIP2101_EPSOIC +Driver_FET:HIP2101_QFN +Driver_FET:HIP2101_SOIC +Driver_FET:HIP4080A +Driver_FET:HIP4081A +Driver_FET:HIP4082xB +Driver_FET:HIP4082xP +Driver_FET:ICL7667 +Driver_FET:IR2010 +Driver_FET:IR2010S +Driver_FET:IR2011 +Driver_FET:IR2085S +Driver_FET:IR2101 +Driver_FET:IR2102 +Driver_FET:IR2103 +Driver_FET:IR2104 +Driver_FET:IR2106 +Driver_FET:IR21064 +Driver_FET:IR2108 +Driver_FET:IR21084 +Driver_FET:IR2109 +Driver_FET:IR21091 +Driver_FET:IR21094 +Driver_FET:IR2110 +Driver_FET:IR2110S +Driver_FET:IR2111 +Driver_FET:IR2112 +Driver_FET:IR2112S +Driver_FET:IR2113 +Driver_FET:IR2113S +Driver_FET:IR2114S +Driver_FET:IR2133 +Driver_FET:IR2133S +Driver_FET:IR2135 +Driver_FET:IR2135S +Driver_FET:IR2153 +Driver_FET:IR21531 +Driver_FET:IR2155 +Driver_FET:IR2181 +Driver_FET:IR21814 +Driver_FET:IR2183 +Driver_FET:IR21834 +Driver_FET:IR2184 +Driver_FET:IR21844 +Driver_FET:IR2213 +Driver_FET:IR2213S +Driver_FET:IR2214S +Driver_FET:IR2233 +Driver_FET:IR2233S +Driver_FET:IR2235 +Driver_FET:IR2235S +Driver_FET:IR2301 +Driver_FET:IR2302 +Driver_FET:IR2304 +Driver_FET:IR2308 +Driver_FET:IR25602S +Driver_FET:IR25603 +Driver_FET:IR25604S +Driver_FET:IR25607S +Driver_FET:IR7106S +Driver_FET:IR7184S +Driver_FET:IR7304S +Driver_FET:IRS2001 +Driver_FET:IRS2001M +Driver_FET:IRS2003 +Driver_FET:IRS2004 +Driver_FET:IRS2005M +Driver_FET:IRS2005S +Driver_FET:IRS2008S +Driver_FET:IRS2011 +Driver_FET:IRS2101 +Driver_FET:IRS2103 +Driver_FET:IRS2104 +Driver_FET:IRS2106 +Driver_FET:IRS21064 +Driver_FET:IRS2108 +Driver_FET:IRS21084 +Driver_FET:IRS2109 +Driver_FET:IRS21091 +Driver_FET:IRS21094 +Driver_FET:IRS2110 +Driver_FET:IRS2110S +Driver_FET:IRS2111 +Driver_FET:IRS2112 +Driver_FET:IRS2112S +Driver_FET:IRS2113 +Driver_FET:IRS2113M +Driver_FET:IRS2113S +Driver_FET:IRS21531D +Driver_FET:IRS2153D +Driver_FET:IRS2181 +Driver_FET:IRS21814 +Driver_FET:IRS21814M +Driver_FET:IRS2183 +Driver_FET:IRS21834 +Driver_FET:IRS2184 +Driver_FET:IRS21844 +Driver_FET:IRS21844M +Driver_FET:IRS2186 +Driver_FET:IRS21864 +Driver_FET:IRS21867S +Driver_FET:IRS2301S +Driver_FET:IRS2302S +Driver_FET:IRS2304 +Driver_FET:IRS2308 +Driver_FET:IRS25606S +Driver_FET:IRS2890DS +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 +Driver_FET:LM5109BSD +Driver_FET:LM5109MA +Driver_FET:LMG1020YFF +Driver_FET:LTC4440EMS8 +Driver_FET:LTC4440ES6 +Driver_FET:LTC4440IMS8 +Driver_FET:LTC4440IS6 +Driver_FET:MAX15012AxSA +Driver_FET:MAX15012BxSA +Driver_FET:MAX15012CxSA +Driver_FET:MAX15012DxSA +Driver_FET:MAX15013AxSA +Driver_FET:MAX15013BxSA +Driver_FET:MAX15013CxSA +Driver_FET:MAX15013DxSA +Driver_FET:MC33152 +Driver_FET:MC34152 +Driver_FET:MCP1415 +Driver_FET:MCP1415R +Driver_FET:MCP1416 +Driver_FET:MCP1416R +Driver_FET:MCP14A0303xMNY +Driver_FET:MCP14A0304xMNY +Driver_FET:MCP14A0305xMNY +Driver_FET:MCP14A0901xMNY +Driver_FET:MCP14A0902xMNY +Driver_FET:MCP14A1201xMNY +Driver_FET:MCP14A1202xMNY +Driver_FET:MIC4426 +Driver_FET:MIC4427 +Driver_FET:MIC4428 +Driver_FET:MIC4604YM +Driver_FET:NCD5702 +Driver_FET:NCV8402xST +Driver_FET:PE29101 +Driver_FET:PE29102 +Driver_FET:PM8834 +Driver_FET:PM8834M +Driver_FET:SM72295MA +Driver_FET:STGAP1AS +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 +Driver_FET:UCC27511ADBV +Driver_FET:UCC27714D +Driver_FET:ZXGD3001E6 +Driver_FET:ZXGD3002E6 +Driver_FET:ZXGD3003E6 +Driver_FET:ZXGD3004E6 +Driver_FET:ZXGD3006E6 +Driver_FET:ZXGD3009E6 +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 +Driver_LED:DIO5661ST6 +Driver_LED:DIO5661TST6 +Driver_LED:HT1632C-52LQFP +Driver_LED:HV9921N8-G +Driver_LED:HV9922N8-G +Driver_LED:HV9923N8-G +Driver_LED:HV9925SG-G +Driver_LED:HV9930LG-G +Driver_LED:HV9931LG-G +Driver_LED:HV9961LG-G +Driver_LED:HV9961NG-G +Driver_LED:HV9967BK7-G +Driver_LED:HV9967BMG-G +Driver_LED:HV9972LG-G +Driver_LED:IS31FL3216 +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 +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 +Driver_LED:LED1642GWXTTR +Driver_LED:LED5000 +Driver_LED:LM3914N +Driver_LED:LM3914V +Driver_LED:LP5036 +Driver_LED:LP8868XQDMT +Driver_LED:LT3465 +Driver_LED:LT3465A +Driver_LED:LT3755xMSE +Driver_LED:LT3755xMSE-1 +Driver_LED:LT3755xMSE-2 +Driver_LED:LT3755xUD +Driver_LED:LT3755xUD-1 +Driver_LED:LT3755xUD-2 +Driver_LED:LT3756xMSE +Driver_LED:LT3756xMSE-1 +Driver_LED:LT3756xMSE-2 +Driver_LED:LT3756xUD +Driver_LED:LT3756xUD-1 +Driver_LED:LT3756xUD-2 +Driver_LED:LT8391xFE +Driver_LED:MAX7219 +Driver_LED:MAX7221xNG +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:STP08CP05B +Driver_LED:STP08CP05M +Driver_LED:STP08CP05T +Driver_LED:STP08CP05XT +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 +Driver_LED:TLC5947RHB +Driver_LED:TLC5949PWP +Driver_LED:TLC5951DAP +Driver_LED:TLC5951RHA +Driver_LED:TLC5951RTA +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 +Driver_Motor:DRV8800RTY +Driver_Motor:DRV8801PWP +Driver_Motor:DRV8801RTY +Driver_Motor:DRV8833PW +Driver_Motor:DRV8833PWP +Driver_Motor:DRV8833RTY +Driver_Motor:DRV8837 +Driver_Motor:DRV8837C +Driver_Motor:DRV8838 +Driver_Motor:DRV8847PWP +Driver_Motor:DRV8847PWR +Driver_Motor:DRV8847RTE +Driver_Motor:DRV8847SPWR +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 +Driver_Motor:L297 +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 +Driver_Motor:SLA7042M +Driver_Motor:SLA7044M +Driver_Motor:SLA7070MPRT +Driver_Motor:SLA7071MPRT +Driver_Motor:SLA7072MPRT +Driver_Motor:SLA7073MPRT +Driver_Motor:SLA7075MPRT +Driver_Motor:SLA7076MPRT +Driver_Motor:SLA7077MPRT +Driver_Motor:SLA7078MPRT +Driver_Motor:SN754410NE +Driver_Motor:STK672-040-E +Driver_Motor:STK672-080-E +Driver_Motor:STSPIN220 +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 +Driver_Motor:VNH5019A-E +Driver_Motor:ZXBM5210-S +Driver_Motor:ZXBM5210-SP +Driver_Relay:DRV8860 +Driver_Relay:DRV8860_PWPR +Driver_Relay:MAX4820xUP +Driver_Relay:MAX4821xUP +Driver_Relay:TPL9201_TSSOP +Driver_TEC:MAX1968xUI +Driver_TEC:MAX1969xUI +DSP_AnalogDevices:ADAU1450 +DSP_AnalogDevices:ADAU1451 +DSP_AnalogDevices:ADAU1452 +DSP_AnalogDevices:ADAU1701 +DSP_AnalogDevices:ADAU1702 +DSP_Freescale:DSP96002 +DSP_Microchip_DSPIC33:DSPIC33EP256MU810-xPT +DSP_Microchip_DSPIC33:DSPIC33FJ128GP204 +DSP_Microchip_DSPIC33:DSPIC33FJ128GP804 +DSP_Microchip_DSPIC33:DSPIC33FJ128MC204 +DSP_Microchip_DSPIC33:DSPIC33FJ128MC510A +DSP_Microchip_DSPIC33:DSPIC33FJ128MC710A +DSP_Microchip_DSPIC33:DSPIC33FJ128MC804 +DSP_Microchip_DSPIC33:DSPIC33FJ256MC510A +DSP_Microchip_DSPIC33:DSPIC33FJ256MC710A +DSP_Microchip_DSPIC33:DSPIC33FJ32GP304 +DSP_Microchip_DSPIC33:DSPIC33FJ32MC304 +DSP_Microchip_DSPIC33:DSPIC33FJ64GP204 +DSP_Microchip_DSPIC33:DSPIC33FJ64GP306A-IMR +DSP_Microchip_DSPIC33:DSPIC33FJ64GP804 +DSP_Microchip_DSPIC33:DSPIC33FJ64MC204 +DSP_Microchip_DSPIC33:DSPIC33FJ64MC510A +DSP_Microchip_DSPIC33:DSPIC33FJ64MC710A +DSP_Microchip_DSPIC33:DSPIC33FJ64MC802-xSP +DSP_Microchip_DSPIC33:DSPIC33FJ64MC804 +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 +Filter:1FP42-3R +Filter:1FP44-2R +Filter:1FP45-0R +Filter:1FP45-1R +Filter:1FP61-4R +Filter:1FP62-3R +Filter:1FP64-2R +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 +Filter:FN405-3-02 +Filter:FN405-6-02 +Filter:FN406-0.5-02 +Filter:FN406-1-02 +Filter:FN406-3-02 +Filter:FN406-6-02 +Filter:FN406-8.4-02 +Filter:FN406B-0.5-02 +Filter:FN406B-1-02 +Filter:FN406B-3-02 +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 +Filter:SAFFA881MFL0F0A +Filter:SAFFA942MFM0F0A +Filter:SAFFB1G58KA0F0A +Filter:SAFFB1G96FN0F0A +Filter:SAFFB2G14FA0F0A +Filter:SAFFB881MFL0F0A +Filter:SAFFB942MFM0F0A +Filter:SF14-1575F5UUA1 +Filter:SF14-1575F5UUC1 +FPGA_CologneChip_GateMate:CCGM1A1 +FPGA_Efinix_Trion:T8Q144xx +FPGA_Lattice:ICE40HX1K-TQ144 +FPGA_Lattice:ICE40HX4K-BG121 +FPGA_Lattice:ICE40HX4K-TQ144 +FPGA_Lattice:ICE40HX8K-BG121 +FPGA_Lattice:ICE40UL1K-SWG16 +FPGA_Lattice:ICE40UP5K-SG48ITR +FPGA_Lattice:ICE5LP1K-SG48 +FPGA_Lattice:LFE5U-85F-6BG381x +FPGA_Lattice:LFE5U-85F-6BG756x +FPGA_Lattice:LFE5U-85F-7BG381x +FPGA_Lattice:LFE5U-85F-7BG756x +FPGA_Lattice:LFE5U-85F-8BG381x +FPGA_Lattice:LFE5U-85F-8BG756x +FPGA_Lattice:LFE5UM-85F-6BG381x +FPGA_Lattice:LFE5UM-85F-6BG756x +FPGA_Lattice:LFE5UM-85F-7BG381x +FPGA_Lattice:LFE5UM-85F-7BG756x +FPGA_Lattice:LFE5UM-85F-8BG381x +FPGA_Lattice:LFE5UM-85F-8BG756x +FPGA_Lattice:LFE5UM5G-85F-8BG381x +FPGA_Lattice:LFE5UM5G-85F-8BG756x +FPGA_Lattice:LFXP2-5E-5TN144 +FPGA_Lattice:LFXP2-5E-6TN144 +FPGA_Lattice:LFXP2-5E-7TN144 +FPGA_Microsemi:A3P030-VQG100 +FPGA_Microsemi:A3P060-VQG100 +FPGA_Microsemi:A3P1000-PQG208 +FPGA_Microsemi:A3P125-PQG208 +FPGA_Microsemi:A3P125-VQG100 +FPGA_Microsemi:A3P250-PQG208 +FPGA_Microsemi:A3P250-VQG100 +FPGA_Microsemi:A3P400-PQG208 +FPGA_Microsemi:A3P600-PQG208 +FPGA_Microsemi:ACT1020PL44 +FPGA_Microsemi:ACT1020PL68 +FPGA_Microsemi:ACT1225PL84 +FPGA_Microsemi:EX128-TQ100 +FPGA_Microsemi:EX128-TQ64 +FPGA_Microsemi:EX256-TQ100 +FPGA_Microsemi:EX64-TQ100 +FPGA_Microsemi:EX64-TQ64 +FPGA_Microsemi:M2GL090T-FG484 +FPGA_Xilinx:XC2018-PC68 +FPGA_Xilinx:XC2018-PC84 +FPGA_Xilinx:XC2064-PC68 +FPGA_Xilinx:XC2C256-TQ144 +FPGA_Xilinx:XC2C256-VQ100 +FPGA_Xilinx:XC2S100TQ144 +FPGA_Xilinx:XC2S150PQ208 +FPGA_Xilinx:XC2S200PQ208 +FPGA_Xilinx:XC2S300PQ208 +FPGA_Xilinx:XC2S400FT256 +FPGA_Xilinx:XC2S50-PQ208 +FPGA_Xilinx:XC2S64A-xQFG48 +FPGA_Xilinx:XC3020-PC68 +FPGA_Xilinx:XC3030-PC44 +FPGA_Xilinx:XC3030-PC68 +FPGA_Xilinx:XC3030-PC84 +FPGA_Xilinx:XC3030-VQ100 +FPGA_Xilinx:XC3042-PC84 +FPGA_Xilinx:XC3042-VQ100 +FPGA_Xilinx:XC3S1400A-FG484 +FPGA_Xilinx:XC3S200AN-FT256 +FPGA_Xilinx:XC3S400-FG320 +FPGA_Xilinx:XC3S400-PQ208 +FPGA_Xilinx:XC3S50-VQ100 +FPGA_Xilinx:XC3S50AN-TQG144 +FPGA_Xilinx:XC4003-PC84 +FPGA_Xilinx:XC4003-VQ100 +FPGA_Xilinx:XC4004-PQ160 +FPGA_Xilinx:XC4005-PC84 +FPGA_Xilinx:XC4005-PG156 +FPGA_Xilinx:XC4005-PQ100 +FPGA_Xilinx:XC4005-PQ160 +FPGA_Xilinx:XC6SLX25T-BG484 +FPGA_Xilinx:XCV150_BG352 +FPGA_Xilinx_Artix7:XC7A100T-CSG324 +FPGA_Xilinx_Artix7:XC7A100T-FGG484 +FPGA_Xilinx_Artix7:XC7A100T-FGG676 +FPGA_Xilinx_Artix7:XC7A100T-FTG256 +FPGA_Xilinx_Artix7:XC7A15T-CPG236 +FPGA_Xilinx_Artix7:XC7A15T-CSG324 +FPGA_Xilinx_Artix7:XC7A15T-CSG325 +FPGA_Xilinx_Artix7:XC7A15T-FGG484 +FPGA_Xilinx_Artix7:XC7A15T-FTG256 +FPGA_Xilinx_Artix7:XC7A200T-FBG484 +FPGA_Xilinx_Artix7:XC7A200T-FBG676 +FPGA_Xilinx_Artix7:XC7A200T-FFG1156 +FPGA_Xilinx_Artix7:XC7A200T-SBG484 +FPGA_Xilinx_Artix7:XC7A35T-CPG236 +FPGA_Xilinx_Artix7:XC7A35T-CSG324 +FPGA_Xilinx_Artix7:XC7A35T-CSG325 +FPGA_Xilinx_Artix7:XC7A35T-FGG484 +FPGA_Xilinx_Artix7:XC7A35T-FTG256 +FPGA_Xilinx_Artix7:XC7A50T-CPG236 +FPGA_Xilinx_Artix7:XC7A50T-CSG324 +FPGA_Xilinx_Artix7:XC7A50T-CSG325 +FPGA_Xilinx_Artix7:XC7A50T-FGG484 +FPGA_Xilinx_Artix7:XC7A50T-FTG256 +FPGA_Xilinx_Artix7:XC7A75T-CSG324 +FPGA_Xilinx_Artix7:XC7A75T-FGG484 +FPGA_Xilinx_Artix7:XC7A75T-FGG676 +FPGA_Xilinx_Artix7:XC7A75T-FTG256 +FPGA_Xilinx_Kintex7:XC7K160T-FBG484 +FPGA_Xilinx_Kintex7:XC7K160T-FBG676 +FPGA_Xilinx_Kintex7:XC7K160T-FFG676 +FPGA_Xilinx_Kintex7:XC7K325T-FBG676 +FPGA_Xilinx_Kintex7:XC7K325T-FBG900 +FPGA_Xilinx_Kintex7:XC7K325T-FFG676 +FPGA_Xilinx_Kintex7:XC7K325T-FFG900 +FPGA_Xilinx_Kintex7:XC7K355T-FFG901 +FPGA_Xilinx_Kintex7:XC7K410T-FBG676 +FPGA_Xilinx_Kintex7:XC7K410T-FBG900 +FPGA_Xilinx_Kintex7:XC7K410T-FFG676 +FPGA_Xilinx_Kintex7:XC7K410T-FFG900 +FPGA_Xilinx_Kintex7:XC7K420T-FFG1156 +FPGA_Xilinx_Kintex7:XC7K420T-FFG901 +FPGA_Xilinx_Kintex7:XC7K480T-FFG1156 +FPGA_Xilinx_Kintex7:XC7K480T-FFG901 +FPGA_Xilinx_Kintex7:XC7K70T-FBG484 +FPGA_Xilinx_Kintex7:XC7K70T-FBG676 +FPGA_Xilinx_Spartan6:XC6SLX100-CSG484 +FPGA_Xilinx_Spartan6:XC6SLX100-FGG484 +FPGA_Xilinx_Spartan6:XC6SLX100-FGG676 +FPGA_Xilinx_Spartan6:XC6SLX100T-CSG484 +FPGA_Xilinx_Spartan6:XC6SLX100T-FGG484 +FPGA_Xilinx_Spartan6:XC6SLX100T-FGG676 +FPGA_Xilinx_Spartan6:XC6SLX100T-FGG900 +FPGA_Xilinx_Spartan6:XC6SLX150-CSG484 +FPGA_Xilinx_Spartan6:XC6SLX150-FGG484 +FPGA_Xilinx_Spartan6:XC6SLX150-FGG676 +FPGA_Xilinx_Spartan6:XC6SLX150-FGG900 +FPGA_Xilinx_Spartan6:XC6SLX150T-CSG484 +FPGA_Xilinx_Spartan6:XC6SLX150T-FGG484 +FPGA_Xilinx_Spartan6:XC6SLX150T-FGG676 +FPGA_Xilinx_Spartan6:XC6SLX150T-FGG900 +FPGA_Xilinx_Spartan6:XC6SLX16-CPG196 +FPGA_Xilinx_Spartan6:XC6SLX16-CSG225 +FPGA_Xilinx_Spartan6:XC6SLX16-CSG324 +FPGA_Xilinx_Spartan6:XC6SLX16-FTG256 +FPGA_Xilinx_Spartan6:XC6SLX25-CSG324 +FPGA_Xilinx_Spartan6:XC6SLX25-FGG484 +FPGA_Xilinx_Spartan6:XC6SLX25-FTG256 +FPGA_Xilinx_Spartan6:XC6SLX25T-CSG324 +FPGA_Xilinx_Spartan6:XC6SLX25T-FGG484 +FPGA_Xilinx_Spartan6:XC6SLX4-CPG196 +FPGA_Xilinx_Spartan6:XC6SLX4-CSG225 +FPGA_Xilinx_Spartan6:XC6SLX4-TQG144 +FPGA_Xilinx_Spartan6:XC6SLX45-CSG324 +FPGA_Xilinx_Spartan6:XC6SLX45-CSG484 +FPGA_Xilinx_Spartan6:XC6SLX45-FGG484 +FPGA_Xilinx_Spartan6:XC6SLX45-FGG676 +FPGA_Xilinx_Spartan6:XC6SLX45T-CSG324 +FPGA_Xilinx_Spartan6:XC6SLX45T-CSG484 +FPGA_Xilinx_Spartan6:XC6SLX45T-FGG484 +FPGA_Xilinx_Spartan6:XC6SLX75-CSG484 +FPGA_Xilinx_Spartan6:XC6SLX75-FGG484 +FPGA_Xilinx_Spartan6:XC6SLX75-FGG676 +FPGA_Xilinx_Spartan6:XC6SLX75T-CSG484 +FPGA_Xilinx_Spartan6:XC6SLX75T-FGG484 +FPGA_Xilinx_Spartan6:XC6SLX75T-FGG676 +FPGA_Xilinx_Spartan6:XC6SLX9-CPG196 +FPGA_Xilinx_Spartan6:XC6SLX9-CSG225 +FPGA_Xilinx_Spartan6:XC6SLX9-CSG324 +FPGA_Xilinx_Spartan6:XC6SLX9-FTG256 +FPGA_Xilinx_Spartan6:XC6SLX9-TQG144 +FPGA_Xilinx_Virtex5:XC5VFX100T-FF1136 +FPGA_Xilinx_Virtex5:XC5VFX100T-FF1738 +FPGA_Xilinx_Virtex5:XC5VFX130T-FF1738 +FPGA_Xilinx_Virtex5:XC5VFX200T-FF1738 +FPGA_Xilinx_Virtex5:XC5VFX30T-FF665 +FPGA_Xilinx_Virtex5:XC5VFX70T-FF1136 +FPGA_Xilinx_Virtex5:XC5VFX70T-FF665 +FPGA_Xilinx_Virtex5:XC5VLX110-FF1153 +FPGA_Xilinx_Virtex5:XC5VLX110-FF1760 +FPGA_Xilinx_Virtex5:XC5VLX110-FF676 +FPGA_Xilinx_Virtex5:XC5VLX110T-FF1136 +FPGA_Xilinx_Virtex5:XC5VLX110T-FF1738 +FPGA_Xilinx_Virtex5:XC5VLX155-FF1153 +FPGA_Xilinx_Virtex5:XC5VLX155-FF1760 +FPGA_Xilinx_Virtex5:XC5VLX155T-FF1136 +FPGA_Xilinx_Virtex5:XC5VLX155T-FF1738 +FPGA_Xilinx_Virtex5:XC5VLX20T-FF323 +FPGA_Xilinx_Virtex5:XC5VLX220-FF1760 +FPGA_Xilinx_Virtex5:XC5VLX220T-FF1738 +FPGA_Xilinx_Virtex5:XC5VLX30-FF324 +FPGA_Xilinx_Virtex5:XC5VLX30-FF676 +FPGA_Xilinx_Virtex5:XC5VLX30T-FF323 +FPGA_Xilinx_Virtex5:XC5VLX30T-FF665 +FPGA_Xilinx_Virtex5:XC5VLX330-FF1760 +FPGA_Xilinx_Virtex5:XC5VLX330T-FF1738 +FPGA_Xilinx_Virtex5:XC5VLX50-FF1153 +FPGA_Xilinx_Virtex5:XC5VLX50-FF324 +FPGA_Xilinx_Virtex5:XC5VLX50-FF676 +FPGA_Xilinx_Virtex5:XC5VLX50T-FF1136 +FPGA_Xilinx_Virtex5:XC5VLX50T-FF665 +FPGA_Xilinx_Virtex5:XC5VLX85-FF1153 +FPGA_Xilinx_Virtex5:XC5VLX85-FF676 +FPGA_Xilinx_Virtex5:XC5VLX85T-FF1136 +FPGA_Xilinx_Virtex5:XC5VSX240T-FF1738 +FPGA_Xilinx_Virtex5:XC5VSX35T-FF665 +FPGA_Xilinx_Virtex5:XC5VSX50T-FF1136 +FPGA_Xilinx_Virtex5:XC5VSX50T-FF665 +FPGA_Xilinx_Virtex5:XC5VSX95T-FF1136 +FPGA_Xilinx_Virtex5:XC5VTX150T-FF1156 +FPGA_Xilinx_Virtex5:XC5VTX150T-FF1759 +FPGA_Xilinx_Virtex5:XC5VTX240T-FF1759 +FPGA_Xilinx_Virtex6:XC6VHX250T-FF1154 +FPGA_Xilinx_Virtex6:XC6VHX255T-FF1155 +FPGA_Xilinx_Virtex6:XC6VHX255T-FF1923 +FPGA_Xilinx_Virtex6:XC6VHX380T-FF1154 +FPGA_Xilinx_Virtex6:XC6VHX380T-FF1155 +FPGA_Xilinx_Virtex6:XC6VHX380T-FF1923 +FPGA_Xilinx_Virtex6:XC6VHX380T-FF1924 +FPGA_Xilinx_Virtex6:XC6VHX565T-FF1923 +FPGA_Xilinx_Virtex6:XC6VHX565T-FF1924 +FPGA_Xilinx_Virtex6:XC6VLX130T-FF1156 +FPGA_Xilinx_Virtex6:XC6VLX130T-FF484 +FPGA_Xilinx_Virtex6:XC6VLX130T-FF784 +FPGA_Xilinx_Virtex6:XC6VLX195T-FF1156 +FPGA_Xilinx_Virtex6:XC6VLX195T-FF784 +FPGA_Xilinx_Virtex6:XC6VLX240T-FF1156 +FPGA_Xilinx_Virtex6:XC6VLX240T-FF1759 +FPGA_Xilinx_Virtex6:XC6VLX240T-FF784 +FPGA_Xilinx_Virtex6:XC6VLX365T-FF1156 +FPGA_Xilinx_Virtex6:XC6VLX365T-FF1759 +FPGA_Xilinx_Virtex6:XC6VLX550T-FF1759 +FPGA_Xilinx_Virtex6:XC6VLX550T-FF1760 +FPGA_Xilinx_Virtex6:XC6VLX75T-FF484 +FPGA_Xilinx_Virtex6:XC6VLX75T-FF784 +FPGA_Xilinx_Virtex6:XC6VLX760-FF1760 +FPGA_Xilinx_Virtex6:XC6VSX315T-FF1156 +FPGA_Xilinx_Virtex6:XC6VSX315T-FF1759 +FPGA_Xilinx_Virtex6:XC6VSX475T-FF1156 +FPGA_Xilinx_Virtex6:XC6VSX475T-FF1759 +FPGA_Xilinx_Virtex7:XC7V2000T-FHG1761 +FPGA_Xilinx_Virtex7:XC7V2000T-FLG1925 +FPGA_Xilinx_Virtex7:XC7V585T-FFG1157 +FPGA_Xilinx_Virtex7:XC7V585T-FFG1761 +FPGA_Xilinx_Virtex7:XC7VH580T-FLG1155 +FPGA_Xilinx_Virtex7:XC7VH580T-FLG1931 +FPGA_Xilinx_Virtex7:XC7VH580T-HCG1155 +FPGA_Xilinx_Virtex7:XC7VH580T-HCG1931 +FPGA_Xilinx_Virtex7:XC7VH870T-FLG1932 +FPGA_Xilinx_Virtex7:XC7VH870T-HCG1932 +FPGA_Xilinx_Virtex7:XC7VX1140T-FLG1926 +FPGA_Xilinx_Virtex7:XC7VX1140T-FLG1928 +FPGA_Xilinx_Virtex7:XC7VX1140T-FLG1930 +FPGA_Xilinx_Virtex7:XC7VX330T-FFG1157 +FPGA_Xilinx_Virtex7:XC7VX330T-FFG1761 +FPGA_Xilinx_Virtex7:XC7VX415T-FFG1157 +FPGA_Xilinx_Virtex7:XC7VX415T-FFG1158 +FPGA_Xilinx_Virtex7:XC7VX415T-FFG1927 +FPGA_Xilinx_Virtex7:XC7VX485T-FFG1157 +FPGA_Xilinx_Virtex7:XC7VX485T-FFG1158 +FPGA_Xilinx_Virtex7:XC7VX485T-FFG1761 +FPGA_Xilinx_Virtex7:XC7VX485T-FFG1927 +FPGA_Xilinx_Virtex7:XC7VX485T-FFG1930 +FPGA_Xilinx_Virtex7:XC7VX550T-FFG1158 +FPGA_Xilinx_Virtex7:XC7VX550T-FFG1927 +FPGA_Xilinx_Virtex7:XC7VX690T-FFG1157 +FPGA_Xilinx_Virtex7:XC7VX690T-FFG1158 +FPGA_Xilinx_Virtex7:XC7VX690T-FFG1761 +FPGA_Xilinx_Virtex7:XC7VX690T-FFG1926 +FPGA_Xilinx_Virtex7:XC7VX690T-FFG1927 +FPGA_Xilinx_Virtex7:XC7VX690T-FFG1930 +FPGA_Xilinx_Virtex7:XC7VX980T-FFG1926 +FPGA_Xilinx_Virtex7:XC7VX980T-FFG1928 +FPGA_Xilinx_Virtex7:XC7VX980T-FFG1930 +GPU:MC6845 +GPU:MC68A45 +GPU:MC68B45 +Graphic:Logo_Open_Hardware_Large +Graphic:Logo_Open_Hardware_Small +Graphic:SYM_Arrow45_Large +Graphic:SYM_Arrow45_Normal +Graphic:SYM_Arrow45_Small +Graphic:SYM_Arrow45_Tiny +Graphic:SYM_Arrow45_XLarge +Graphic:SYM_Arrow_Large +Graphic:SYM_Arrow_Normal +Graphic:SYM_Arrow_Small +Graphic:SYM_Arrow_Tiny +Graphic:SYM_Arrow_XLarge +Graphic:SYM_ESD_Large +Graphic:SYM_ESD_Small +Graphic:SYM_Earth_Protective_Large +Graphic:SYM_Earth_Protective_Small +Graphic:SYM_EasterEgg_42x60mm +Graphic:SYM_Flash_Large +Graphic:SYM_Flash_Small +Graphic:SYM_Flash_XLarge +Graphic:SYM_Hot_Large +Graphic:SYM_Hot_Small +Graphic:SYM_LASER_Large +Graphic:SYM_LASER_Small +Graphic:SYM_Magnet_Large +Graphic:SYM_Magnet_Small +Graphic:SYM_Radio_Waves_Large +Graphic:SYM_Radio_Waves_Small +Graphic:SYM_Radioactive_Large +Graphic:SYM_Radioactive_Radiation_Small +Interface:5PB1108PGxx +Interface:6821 +Interface:6822 +Interface:68230 +Interface:68681 +Interface:68901_PLCC +Interface:8237 +Interface:8255 +Interface:8255A +Interface:8259 +Interface:8259A +Interface:8259A-2 +Interface:8288 +Interface:82C55A +Interface:82C55A_PLCC +Interface:88SE9125C0-NAA +Interface:AD9833xRM +Interface:AD9834 +Interface:AD9850 +Interface:AD9851 +Interface:AD9910 +Interface:AD9912 +Interface:AD9951 +Interface:AD9954 +Interface:AM26LS31CD +Interface:AM26LS31CDB +Interface:AM26LS31CN +Interface:AM26LS31MJ +Interface:AM26LS31xNS +Interface:AM26LV32xD +Interface:AM26LV32xNS +Interface:CDCLVP1102RGT +Interface:CH376T +Interface:DS90C124 +Interface:DS90C241 +Interface:DS90C402 +Interface:DS90LV011A +Interface:DS90LV027A +Interface:FD1771 +Interface:FIN1019M +Interface:FIN1019MTC +Interface:HT12D +Interface:HT12E +Interface:LTC1518 +Interface:LTC1519 +Interface:LTC1688 +Interface:LTC1689 +Interface:LTC6957xDD-1 +Interface:LTC6957xDD-2 +Interface:LTC6957xDD-3 +Interface:LTC6957xDD-4 +Interface:LTC6957xMS-1 +Interface:LTC6957xMS-2 +Interface:LTC6957xMS-3 +Interface:LTC6957xMS-4 +Interface:MAX6816 +Interface:MC100EPT22D +Interface:MC100EPT22DT +Interface:MC100LVELT22D +Interface:MC100LVELT22DT +Interface:MC6840 +Interface:MC6843 +Interface:MC6844 +Interface:MC68A21 +Interface:MC68A40 +Interface:MC68A44 +Interface:MC68B21 +Interface:MC68B40 +Interface:MC68B44 +Interface:NB3N551MN +Interface:ONET1191PRGT +Interface:PCA9306 +Interface:PCA9306D +Interface:PCA9306DC +Interface:PCA9306DC1 +Interface:PCA9306DP +Interface:PCA9600D +Interface:PCA9600DP +Interface:PCA9615DP +Interface:PCI9030-PQFP176 +Interface:S5933_PQ160 +Interface:SI9986 +Interface:SLB9660xT +Interface:SLB9665xT +Interface:SN65LVDS047D +Interface:SN65LVDS047PW +Interface:SN65LVDS1D +Interface:SN65LVDS1DBV +Interface:SN65LVDS2D +Interface:SN65LVDS2DBV +Interface:SN65LVDT2D +Interface:SN65LVDT2DBV +Interface:SN74LV8153N +Interface:SN74LV8153PW +Interface:SN75160BDW +Interface:SN75160BN +Interface:TB5D1MD +Interface:TB5D1MDW +Interface:TB5D2H +Interface:TB5D2HDW +Interface:TB5R1D +Interface:TB5R1DW +Interface:TB5R2D +Interface:TB5R2DW +Interface:TCA9406DC +Interface:TCA9800 +Interface:TCA9801 +Interface:TCA9802 +Interface:TCA9803 +Interface:U2270B +Interface:WD2791 +Interface:WD2793 +Interface:WD2795 +Interface:WD2797 +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 +Interface_CAN_LIN:MCP2021A-xxxxP +Interface_CAN_LIN:MCP2021A-xxxxSN +Interface_CAN_LIN:MCP2022A-xxxxP +Interface_CAN_LIN:MCP2022A-xxxxSL +Interface_CAN_LIN:MCP2022A-xxxxST +Interface_CAN_LIN:MCP2050-330-EMQ +Interface_CAN_LIN:MCP2050-330-EP +Interface_CAN_LIN:MCP2050-330-ESL +Interface_CAN_LIN:MCP2050-500-EMQ +Interface_CAN_LIN:MCP2050-500-EP +Interface_CAN_LIN:MCP2050-500-ESL +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 +Interface_CAN_LIN:MCP2551-I-SN +Interface_CAN_LIN:MCP2557FD-xMF +Interface_CAN_LIN:MCP2557FD-xMNY +Interface_CAN_LIN:MCP2557FD-xSN +Interface_CAN_LIN:MCP2558FD-xMF +Interface_CAN_LIN:MCP2558FD-xMNY +Interface_CAN_LIN:MCP2558FD-xSN +Interface_CAN_LIN:MCP2561-E-MF +Interface_CAN_LIN:MCP2561-E-P +Interface_CAN_LIN:MCP2561-E-SN +Interface_CAN_LIN:MCP2561-H-MF +Interface_CAN_LIN:MCP2561-H-P +Interface_CAN_LIN:MCP2561-H-SN +Interface_CAN_LIN:MCP2562-E-MF +Interface_CAN_LIN:MCP2562-E-P +Interface_CAN_LIN:MCP2562-E-SN +Interface_CAN_LIN:MCP2562-H-MF +Interface_CAN_LIN:MCP2562-H-P +Interface_CAN_LIN:MCP2562-H-SN +Interface_CAN_LIN:MCP25625-x-SS +Interface_CAN_LIN:PCA82C251 +Interface_CAN_LIN:SN65HVD1050D +Interface_CAN_LIN:SN65HVD230 +Interface_CAN_LIN:SN65HVD231 +Interface_CAN_LIN:SN65HVD232 +Interface_CAN_LIN:SN65HVD233 +Interface_CAN_LIN:SN65HVD234 +Interface_CAN_LIN:SN65HVD235 +Interface_CAN_LIN:SN65HVD255D +Interface_CAN_LIN:SN65HVD256D +Interface_CAN_LIN:SN65HVD257D +Interface_CAN_LIN:TCAN1043xDxQ1 +Interface_CAN_LIN:TCAN330 +Interface_CAN_LIN:TCAN330G +Interface_CAN_LIN:TCAN332 +Interface_CAN_LIN:TCAN332G +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 +Interface_CAN_LIN:TJA1029TK +Interface_CAN_LIN:TJA1042T +Interface_CAN_LIN:TJA1042T-3 +Interface_CAN_LIN:TJA1042TK-3 +Interface_CAN_LIN:TJA1043T +Interface_CAN_LIN:TJA1043TK +Interface_CAN_LIN:TJA1049T +Interface_CAN_LIN:TJA1049T-3 +Interface_CAN_LIN:TJA1049TK +Interface_CAN_LIN:TJA1049TK-3 +Interface_CAN_LIN:TJA1051T +Interface_CAN_LIN:TJA1051T-3 +Interface_CAN_LIN:TJA1051T-E +Interface_CAN_LIN:TJA1051TK-3 +Interface_CAN_LIN:TJA1052i-1 +Interface_CAN_LIN:TJA1052i-2 +Interface_CAN_LIN:TJA1052i-5 +Interface_CAN_LIN:TJA1145T +Interface_CAN_LIN:TJA1145T-FD +Interface_CAN_LIN:TJA1145TK +Interface_CAN_LIN:TJA1145TK-FD +Interface_CurrentLoop:XTR111AxDGQ +Interface_CurrentLoop:XTR115U +Interface_CurrentLoop:XTR116U +Interface_Ethernet:DP83848C +Interface_Ethernet:DP83848I +Interface_Ethernet:ENC28J60x-ML +Interface_Ethernet:ENC28J60x-SO +Interface_Ethernet:ENC28J60x-SP +Interface_Ethernet:ENC28J60x-SS +Interface_Ethernet:ENC424J600-ML +Interface_Ethernet:ENC424J600-PT +Interface_Ethernet:KSZ8081MLX +Interface_Ethernet:KSZ8081RNA +Interface_Ethernet:KSZ8081RND +Interface_Ethernet:KSZ9031RNXCA +Interface_Ethernet:KSZ9563RNX +Interface_Ethernet:KSZ9893RNX +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 +Interface_Ethernet:LAN9513i +Interface_Ethernet:LAN9514 +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 +Interface_Expansion:MCP23008-xSO +Interface_Expansion:MCP23008-xSS +Interface_Expansion:MCP23017_ML +Interface_Expansion:MCP23017_SO +Interface_Expansion:MCP23017_SP +Interface_Expansion:MCP23017_SS +Interface_Expansion:MCP23S17_ML +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 +Interface_Expansion:PCA9537 +Interface_Expansion:PCA9544AD +Interface_Expansion:PCA9544APW +Interface_Expansion:PCA9547BS +Interface_Expansion:PCA9547D +Interface_Expansion:PCA9547PW +Interface_Expansion:PCA9548ADB +Interface_Expansion:PCA9548ADW +Interface_Expansion:PCA9548APW +Interface_Expansion:PCA9548ARGE +Interface_Expansion:PCA9555D +Interface_Expansion:PCA9555DB +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: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 +Interface_Expansion:TCA9534 +Interface_Expansion:TCA9535DBR +Interface_Expansion:TCA9535DBT +Interface_Expansion:TCA9535MRGER +Interface_Expansion:TCA9535PWR +Interface_Expansion:TCA9535RGER +Interface_Expansion:TCA9535RTWR +Interface_Expansion:TCA9544A +Interface_Expansion:TCA9548AMRGER +Interface_Expansion:TCA9548APWR +Interface_Expansion:TCA9548ARGER +Interface_Expansion:TCA9554DB +Interface_Expansion:TCA9554DBQ +Interface_Expansion:TCA9554DW +Interface_Expansion:TCA9554PW +Interface_Expansion:TCA9555DBR +Interface_Expansion:TCA9555DBT +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 +Interface_HID:JoyWarrior24A8L +Interface_HID:SpinWarrior24A3 +Interface_HID:SpinWarrior24R4 +Interface_HID:SpinWarrior24R6 +Interface_LineDriver:DS7820 +Interface_LineDriver:DS7830 +Interface_LineDriver:DS8830 +Interface_LineDriver:DS89C21 +Interface_LineDriver:EL7242C +Interface_LineDriver:MC3486N +Interface_LineDriver:MC3487DX +Interface_LineDriver:MC3487N +Interface_LineDriver:UA9637 +Interface_LineDriver:UA9638CD +Interface_LineDriver:UA9638CDE4 +Interface_LineDriver:UA9638CDG4 +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 +Interface_Optical:QSE159 +Interface_Optical:SFP +Interface_Optical:SFP+ +Interface_Optical:TSDP341xx +Interface_Optical:TSDP343xx +Interface_Optical:TSMP58000 +Interface_Optical:TSMP58138 +Interface_Optical:TSOP17xx +Interface_Optical:TSOP21xx +Interface_Optical:TSOP23xx +Interface_Optical:TSOP25xx +Interface_Optical:TSOP312xx +Interface_Optical:TSOP314xx +Interface_Optical:TSOP321xx +Interface_Optical:TSOP323xx +Interface_Optical:TSOP325xx +Interface_Optical:TSOP32S40F +Interface_Optical:TSOP331xx +Interface_Optical:TSOP333xx +Interface_Optical:TSOP335xx +Interface_Optical:TSOP341xx +Interface_Optical:TSOP343xx +Interface_Optical:TSOP345xx +Interface_Optical:TSOP348xx +Interface_Optical:TSOP34S40F +Interface_Optical:TSOP382xx +Interface_Optical:TSOP384xx +Interface_Optical:TSOP38G36 +Interface_Optical:TSOP41xx +Interface_Optical:TSOP43xx +Interface_Optical:TSOP45xx +Interface_Optical:TSOP531xx +Interface_Optical:TSOP533xx +Interface_Optical:TSOP535xx +Interface_Optical:TSOP581xx +Interface_Optical:TSOP582xx +Interface_Optical:TSOP583xx +Interface_Optical:TSOP584xx +Interface_Optical:TSOP585xx +Interface_Telecom:FX614 +Interface_Telecom:HT9170D +Interface_Telecom:Si3210 +Interface_UART:16450 +Interface_UART:16550 +Interface_UART:68C681 +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 +Interface_UART:ADM2481xRW +Interface_UART:ADM2483xRW +Interface_UART:ADM2484E +Interface_UART:ADM2582E +Interface_UART:ADM2587E +Interface_UART:ADM2682E +Interface_UART:ADM2687E +Interface_UART:ADM3488ExR +Interface_UART:ADM3490ExR +Interface_UART:ADM3491ExR +Interface_UART:AZ75232G +Interface_UART:AZ75232GS +Interface_UART:AZ75232M +Interface_UART:GD65232DB +Interface_UART:GD65232DW +Interface_UART:GD65232PW +Interface_UART:GD75232DB +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 +Interface_UART:ISL3283ExHZ +Interface_UART:ISL3284ExHZ +Interface_UART:ISL3295xxH +Interface_UART:ISL3298xxRT +Interface_UART:ISL83491 +Interface_UART:ISO1500 +Interface_UART:ISO3082DW +Interface_UART:ISO3088DW +Interface_UART:LT1080 +Interface_UART:LT1785AxN8 +Interface_UART:LT1785AxS8 +Interface_UART:LT1785xN8 +Interface_UART:LT1785xS8 +Interface_UART:LT1791AxN8 +Interface_UART:LT1791AxS +Interface_UART:LT1791xN8 +Interface_UART:LT1791xS +Interface_UART:LTC2850xDD +Interface_UART:LTC2850xMS8 +Interface_UART:LTC2850xS8 +Interface_UART:LTC2851xDD +Interface_UART:LTC2851xMS8 +Interface_UART:LTC2851xS8 +Interface_UART:LTC2852xDD +Interface_UART:LTC2852xMS +Interface_UART:LTC2852xS +Interface_UART:LTC2856xDD-1 +Interface_UART:LTC2856xDD-2 +Interface_UART:LTC2856xMS8-1 +Interface_UART:LTC2856xMS8-2 +Interface_UART:LTC2857xDD-1 +Interface_UART:LTC2857xDD-2 +Interface_UART:LTC2857xMS8-1 +Interface_UART:LTC2857xMS8-2 +Interface_UART:LTC2858xDD-1 +Interface_UART:LTC2858xDD-2 +Interface_UART:LTC2858xMS-1 +Interface_UART:LTC2858xMS-2 +Interface_UART:LTC2861 +Interface_UART:MAX13432EESD +Interface_UART:MAX13432EETD +Interface_UART:MAX13433EESD +Interface_UART:MAX13433EETD +Interface_UART:MAX14783ExS +Interface_UART:MAX14830 +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 +Interface_UART:MAX3078E +Interface_UART:MAX3218 +Interface_UART:MAX3221 +Interface_UART:MAX3226 +Interface_UART:MAX3227 +Interface_UART:MAX3232 +Interface_UART:MAX3284E +Interface_UART:MAX3483 +Interface_UART:MAX3485 +Interface_UART:MAX3486 +Interface_UART:MAX3488xPA +Interface_UART:MAX3488xSA +Interface_UART:MAX3490xPA +Interface_UART:MAX3490xSA +Interface_UART:MAX481E +Interface_UART:MAX483E +Interface_UART:MAX485E +Interface_UART:MAX487E +Interface_UART:MAX488E +Interface_UART:MAX489E +Interface_UART:MAX490E +Interface_UART:MAX491E +Interface_UART:MC6850 +Interface_UART:MC68A50 +Interface_UART:MC68B50 +Interface_UART:SC16IS740 +Interface_UART:SC16IS750xBS +Interface_UART:SC16IS750xPW +Interface_UART:SC16IS752IBS +Interface_UART:SC16IS752IPW +Interface_UART:SC16IS760xBS +Interface_UART:SC16IS760xPW +Interface_UART:SC16IS762IBS +Interface_UART:SC16IS762IPW +Interface_UART:SN65HVD11HD +Interface_UART:SN65LBC176D +Interface_UART:SN65LBC176P +Interface_UART:SN65LBC176QD +Interface_UART:SN65LBC176QDR +Interface_UART:SN75176AD +Interface_UART:SN75176AP +Interface_UART:SN75LBC176D +Interface_UART:SN75LBC176P +Interface_UART:SNJ55LBC176JG +Interface_UART:SP3481CN +Interface_UART:SP3481CP +Interface_UART:SP3481EN +Interface_UART:SP3481EP +Interface_UART:SP3485CN +Interface_UART:SP3485CP +Interface_UART:SP3485EN +Interface_UART:SP3485EP +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 +Interface_USB:CP2102N-Axx-xQFN28 +Interface_USB:CP2104 +Interface_USB:CP2108-xxx-xM +Interface_USB:CP2112 +Interface_USB:CP2615-xx-xM +Interface_USB:CY7C65211-24LTXI +Interface_USB:CY7C65211A-24LTXI +Interface_USB:CY7C65213-28PVXI +Interface_USB:CY7C65213-32LTXI +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 +Interface_USB:FSUSB42MUX +Interface_USB:FT200XD +Interface_USB:FT201XQ +Interface_USB:FT201XS +Interface_USB:FT220XQ +Interface_USB:FT220XS +Interface_USB:FT221XQ +Interface_USB:FT221XS +Interface_USB:FT2232D +Interface_USB:FT2232HL +Interface_USB:FT2232HQ +Interface_USB:FT230XQ +Interface_USB:FT230XS +Interface_USB:FT231XQ +Interface_USB:FT231XS +Interface_USB:FT232BM +Interface_USB:FT232H +Interface_USB:FT232RL +Interface_USB:FT234XD +Interface_USB:FT240XQ +Interface_USB:FT240XS +Interface_USB:FT245BM +Interface_USB:FT4222HQ +Interface_USB:FT4232H +Interface_USB:FT601Q +Interface_USB:FUSB302B01MPX +Interface_USB:FUSB302B10MPX +Interface_USB:FUSB302B11MPX +Interface_USB:FUSB302BMPX +Interface_USB:FUSB303BTMX +Interface_USB:FUSB307BMPX +Interface_USB:IP2721 +Interface_USB:MA8601 +Interface_USB:MCP2200-E-SS +Interface_USB:MCP2200-I-MQ +Interface_USB:MCP2200-I-SO +Interface_USB:MCP2200-I-SS +Interface_USB:MCP2200T-E-SS +Interface_USB:MCP2200T-I-MQ +Interface_USB:MCP2200T-I-SO +Interface_USB:MCP2200T-I-SS +Interface_USB:MCP2210x-MQ +Interface_USB:MCP2210x-SO +Interface_USB:MCP2210x-SS +Interface_USB:MCP2221AxML +Interface_USB:MCP2221AxP +Interface_USB:MCP2221AxSL +Interface_USB:MCP2221AxST +Interface_USB:MP5034GJ +Interface_USB:STULPI01A +Interface_USB:STULPI01B +Interface_USB:STUSB4500QTR +Interface_USB:TPS2500DRC +Interface_USB:TPS2501DRC +Interface_USB:TPS2513 +Interface_USB:TPS2513A +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 +Interface_USB:TUSB321 +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 +Interface_USB:USB3341 +Interface_USB:USB3343 +Interface_USB:USB3346 +Interface_USB:USB3347 +Interface_USB:USB3740B-AI2 +Interface_USB:USB3740B-AI9 +Interface_USB:XR21B1424 +Isolator:4N25 +Isolator:4N26 +Isolator:4N27 +Isolator:4N28 +Isolator:4N35 +Isolator:4N36 +Isolator:4N37 +Isolator:6N135 +Isolator:6N135S +Isolator:6N136 +Isolator:6N136S +Isolator:6N137 +Isolator:6N138 +Isolator:6N139 +Isolator:ACPL-214-500E +Isolator:ADN4650 +Isolator:ADN4651 +Isolator:ADN4652 +Isolator:ADuM1200AR +Isolator:ADuM1200BR +Isolator:ADuM1200CR +Isolator:ADuM1200WS +Isolator:ADuM1200WT +Isolator:ADuM1200WU +Isolator:ADuM1201AR +Isolator:ADuM1201BR +Isolator:ADuM1201CR +Isolator:ADuM1201WS +Isolator:ADuM1201WT +Isolator:ADuM1201WU +Isolator:ADuM120N +Isolator:ADuM121N +Isolator:ADuM1250 +Isolator:ADuM1281 +Isolator:ADuM1300xRW +Isolator:ADuM1400xRW +Isolator:ADuM1401xRW +Isolator:ADuM1402xRW +Isolator:ADuM1410 +Isolator:ADuM1411 +Isolator:ADuM1412 +Isolator:ADuM260N +Isolator:ADuM261N +Isolator:ADuM262N +Isolator:ADuM263N +Isolator:ADuM3151 +Isolator:ADuM3152 +Isolator:ADuM3153 +Isolator:ADuM5211 +Isolator:ADuM5401 +Isolator:ADuM5402 +Isolator:ADuM5403 +Isolator:ADuM5404 +Isolator:ADuM5410 +Isolator:ADuM5411 +Isolator:ADuM5412 +Isolator:ADuM7640A +Isolator:ADuM7640C +Isolator:ADuM7641A +Isolator:ADuM7641C +Isolator:ADuM7642A +Isolator:ADuM7642C +Isolator:ADuM7643A +Isolator:ADuM7643C +Isolator:CNY17-1 +Isolator:CNY17-2 +Isolator:CNY17-3 +Isolator:CNY17-4 +Isolator:CPC-5002 +Isolator:EL814 +Isolator:EL817 +Isolator:FODM214 +Isolator:FODM214A +Isolator:FODM217A +Isolator:FODM217B +Isolator:FODM217C +Isolator:FODM217D +Isolator:H11AA1 +Isolator:H11L1 +Isolator:H11L2 +Isolator:H11L3 +Isolator:HCNW2201 +Isolator:HCNW2211 +Isolator:HCPL-0201 +Isolator:HCPL-0211 +Isolator:HCPL-0600 +Isolator:HCPL-0601 +Isolator:HCPL-0611 +Isolator:HCPL-061A +Isolator:HCPL-061N +Isolator:HCPL-0630 +Isolator:HCPL-0631 +Isolator:HCPL-063A +Isolator:HCPL-063N +Isolator:HCPL-0661 +Isolator:HCPL-2201 +Isolator:HCPL-2202 +Isolator:HCPL-2211 +Isolator:HCPL-2212 +Isolator:HCPL-2601 +Isolator:HCPL-2611 +Isolator:HCPL-261A +Isolator:HCPL-261N +Isolator:HCPL-2630 +Isolator:HCPL-2631 +Isolator:HCPL-263A +Isolator:HCPL-263N +Isolator:HCPL-4661 +Isolator:HCPL-9000 +Isolator:HCPL2730 +Isolator:HCPL2731 +Isolator:ILD74 +Isolator:ILQ74 +Isolator:ISO1211 +Isolator:ISO1212 +Isolator:ISO1540 +Isolator:ISO1541 +Isolator:ISO1642DWR +Isolator:ISO1643DWR +Isolator:ISO1644DWR +Isolator:ISO6731 +Isolator:ISO6740 +Isolator:ISO6741 +Isolator:ISO6742 +Isolator:ISO7320C +Isolator:ISO7320FC +Isolator:ISO7321C +Isolator:ISO7321FC +Isolator:ISO7330C +Isolator:ISO7330FC +Isolator:ISO7331C +Isolator:ISO7331FC +Isolator:ISO7340C +Isolator:ISO7340FC +Isolator:ISO7341C +Isolator:ISO7341FC +Isolator:ISO7342C +Isolator:ISO7342FC +Isolator:ISO7760DBQ +Isolator:ISO7760DW +Isolator:ISO7760FDBQ +Isolator:ISO7760FDW +Isolator:ISO7761DBQ +Isolator:ISO7761DW +Isolator:ISO7761FDBQ +Isolator:ISO7761FDW +Isolator:ISO7762DBQ +Isolator:ISO7762DW +Isolator:ISO7762FDBQ +Isolator:ISO7762FDW +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 +Isolator:LTV-355T +Isolator:LTV-356T +Isolator:LTV-357T +Isolator:LTV-358T +Isolator:LTV-814 +Isolator:LTV-817 +Isolator:LTV-817M +Isolator:LTV-817S +Isolator:LTV-824 +Isolator:LTV-827 +Isolator:LTV-827M +Isolator:LTV-827S +Isolator:LTV-844 +Isolator:LTV-847 +Isolator:LTV-847M +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 +Isolator:PC817 +Isolator:PC827 +Isolator:PC837 +Isolator:PC847 +Isolator:PS8802-1 +Isolator:PS8802-2 +Isolator:SFH617A-1 +Isolator:SFH617A-1X001 +Isolator:SFH617A-1X006 +Isolator:SFH617A-1X007T +Isolator:SFH617A-1X016 +Isolator:SFH617A-2 +Isolator:SFH617A-2X001 +Isolator:SFH617A-2X006 +Isolator:SFH617A-2X009T +Isolator:SFH617A-2X016 +Isolator:SFH617A-2X017T +Isolator:SFH617A-2X019T +Isolator:SFH617A-3 +Isolator:SFH617A-3X001 +Isolator:SFH617A-3X006 +Isolator:SFH617A-3X007T +Isolator:SFH617A-3X016 +Isolator:SFH617A-3X017T +Isolator:SFH617A-4 +Isolator:SFH617A-4X001 +Isolator:SFH617A-4X006 +Isolator:SFH617A-4X016 +Isolator:SFH6206-1T +Isolator:SFH6206-2T +Isolator:SFH6206-2X001T +Isolator:SFH6206-3T +Isolator:SFH6206-3X001T +Isolator:SFH620A-1 +Isolator:SFH620A-1X001 +Isolator:SFH620A-1X006 +Isolator:SFH620A-2 +Isolator:SFH620A-2X001 +Isolator:SFH620A-2X006 +Isolator:SFH620A-2X007T +Isolator:SFH620A-2X016 +Isolator:SFH620A-2X017T +Isolator:SFH620A-3 +Isolator:SFH620A-3X001 +Isolator:SFH620A-3X006 +Isolator:SFH620A-3X016 +Isolator:Si8640BA-B-IU +Isolator:Si8640BB-B-IS +Isolator:Si8640BB-B-IS1 +Isolator:Si8640BB-B-IU +Isolator:Si8640BC-B-IS1 +Isolator:Si8640BD-B-IS +Isolator:Si8640BD-B-IS2 +Isolator:Si8640EB-B-IU +Isolator:Si8640EC-B-IS1 +Isolator:Si8640ED-B-IS +Isolator:Si8640ED-B-IS2 +Isolator:Si8641BA-B-IU +Isolator:Si8641BB-B-IS +Isolator:Si8641BB-B-IS1 +Isolator:Si8641BB-B-IU +Isolator:Si8641BC-B-IS1 +Isolator:Si8641BD-B-IS +Isolator:Si8641BD-B-IS2 +Isolator:Si8641EB-B-IU +Isolator:Si8641EC-B-IS1 +Isolator:Si8641ED-B-IS +Isolator:Si8641ED-B-IS2 +Isolator:Si8642BA-B-IU +Isolator:Si8642BB-B-IS +Isolator:Si8642BB-B-IS1 +Isolator:Si8642BB-B-IU +Isolator:Si8642BC-B-IS1 +Isolator:Si8642BD-B-IS +Isolator:Si8642BD-B-IS2 +Isolator:Si8642EA-B-IU +Isolator:Si8642EB-B-IU +Isolator:Si8642EC-B-IS1 +Isolator:Si8642ED-B-IS +Isolator:Si8642ED-B-IS2 +Isolator:Si8645BA-B-IU +Isolator:Si8645BB-B-IS +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 +Isolator:TCMT1103 +Isolator:TCMT1104 +Isolator:TCMT1105 +Isolator:TCMT1106 +Isolator:TCMT1107 +Isolator:TCMT1108 +Isolator:TCMT1109 +Isolator:TCMT1600 +Isolator:TCMT4100 +Isolator:TCMT4106 +Isolator:TCMT4600 +Isolator:TCMT4606 +Isolator:TIL111 +Isolator:TLP127 +Isolator:TLP130 +Isolator:TLP131 +Isolator:TLP137 +Isolator:TLP184 +Isolator:TLP184xSE +Isolator:TLP185 +Isolator:TLP185xSE +Isolator:TLP2310 +Isolator:TLP2703 +Isolator:TLP2745 +Isolator:TLP2748 +Isolator:TLP2761 +Isolator:TLP2767 +Isolator:TLP2768A +Isolator:TLP2770 +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 +Isolator:TLP627 +Isolator:TLP627-2 +Isolator:TLP627-4 +Isolator:TLP785 +Isolator:TLP785F +Isolator:VO0600T +Isolator:VO0601T +Isolator:VO0611T +Isolator:VO0630T +Isolator:VO0631T +Isolator:VO0661T +Isolator:VO2601 +Isolator:VO2611 +Isolator:VO2630 +Isolator:VO2631 +Isolator:VO4661 +Isolator:VO615A +Isolator:VO615A-1 +Isolator:VO615A-2 +Isolator:VO615A-3 +Isolator:VO615A-4 +Isolator:VO615A-5 +Isolator:VO615A-6 +Isolator:VO615A-7 +Isolator:VO615A-8 +Isolator:VO615A-9 +Isolator:VOA300 +Isolator:VOS618A +Isolator:VTL5C +Isolator:VTL5Cx2 +Isolator:π120U30 +Isolator:π120U31 +Isolator_Analog:ACPL-C790 +Isolator_Analog:ACPL-C79A +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 +Jumper:SolderJumper_2_Open +Jumper:SolderJumper_3_Bridged12 +Jumper:SolderJumper_3_Bridged123 +Jumper:SolderJumper_3_Open +LED:APA-106-F5 +LED:APA102 +LED:APA102-2020 +LED:APFA3010 +LED:ASMB-MTB0-0A3A2 +LED:ASMB-MTB1-0A3A2 +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 +LED:HDSP-4832 +LED:HDSP-4832_2 +LED:HDSP-4836 +LED:HDSP-4836_2 +LED:HDSP-4840 +LED:HDSP-4840_2 +LED:HDSP-4850 +LED:HDSP-4850_2 +LED:HLCP-J100 +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 +LED:LD274 +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 +LED:SFH4346 +LED:SFH4356P +LED:SFH4546 +LED:SFH4550 +LED:SFH460 +LED:SFH480 +LED:SFH482 +LED:SK6805 +LED:SK6812 +LED:SK6812MINI +LED:SMLVN6RGB +LED:TSAL4400 +LED:WS2812 +LED:WS2812B +LED:WS2812B-2020 +LED:WS2812S +LED:WS2813 +LED:WS2822S +Logic_LevelTranslator:74LVC2T45DC +Logic_LevelTranslator:74LVCH2T45DC +Logic_LevelTranslator:FXMA108 +Logic_LevelTranslator:NCN4555MN +Logic_LevelTranslator:NLSV2T244D +Logic_LevelTranslator:NLSV2T244DM +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 +Logic_LevelTranslator:TXB0104D +Logic_LevelTranslator:TXB0104PW +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 +Logic_LevelTranslator:TXS0101DCK +Logic_LevelTranslator:TXS0101DRL +Logic_LevelTranslator:TXS0101YZP +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 +MCU_Cypress:CY7C68014A-56PVX +MCU_Cypress:CY8C4127LQI-BL453 +MCU_Cypress:CY8C4127LQI-BL473 +MCU_Cypress:CY8C4127LQI-BL483 +MCU_Cypress:CY8C4127LQI-BL493 +MCU_Cypress:CY8C4245AXI-M445 +MCU_Cypress:CY8C4245AZI-M445 +MCU_Cypress:CY8C4246AXI-M445 +MCU_Cypress:CY8C4246AZI-M445 +MCU_Cypress:CY8C4246AZI-M475 +MCU_Cypress:CY8C4247AXI-M485 +MCU_Cypress:CY8C4247AZI-M475 +MCU_Cypress:CY8C4247AZI-M485 +MCU_Cypress:CY8C4247LQI-BL453 +MCU_Cypress:CY8C4247LQI-BL463 +MCU_Cypress:CY8C4247LQI-BL473 +MCU_Cypress:CY8C4247LQI-BL483 +MCU_Cypress:CY8C4247LQI-BL493 +MCU_Cypress:CY8C4247LQQ-BL483 +MCU_Cypress:CY8C4xx7LQI-4xx +MCU_Cypress:CYBL10161-56LQXI +MCU_Cypress:CYBL10162-56LQXI +MCU_Cypress:CYBL10163-56LQXI +MCU_Cypress:CYBL10461-56LQXI +MCU_Cypress:CYBL10462-56LQXI +MCU_Cypress:CYBL10463-56LQXI +MCU_Cypress:CYBL10561-56LQXI +MCU_Cypress:CYBL10562-56LQXI +MCU_Cypress:CYBL10563-56LQXI +MCU_Cypress:CYBL10563-56LQXQ +MCU_Cypress:CYBL10563-68FLXIT +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 +MCU_Intel:8035 +MCU_Intel:8039 +MCU_Intel:8040 +MCU_Intel:8048 +MCU_Intel:8049 +MCU_Intel:8050 +MCU_Intel:8080 +MCU_Intel:8080A +MCU_Intel:8086_Max_Mode +MCU_Intel:8086_Min_Mode +MCU_Intel:8087 +MCU_Intel:8088 +MCU_Intel:8088_Max_Mode +MCU_Intel:8088_Min_Mode +MCU_Intel:80C186XL +MCU_Intel:80C188 +MCU_Intel:80C188XL +MCU_Intel:8748 +MCU_Intel:8749 +MCU_Intel:I386EX_PQFP +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 +MCU_Microchip_8051:AT89C2051-24S +MCU_Microchip_8051:AT89C4051-12P +MCU_Microchip_8051:AT89C4051-12S +MCU_Microchip_8051:AT89C4051-24P +MCU_Microchip_8051:AT89C4051-24S +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 +MCU_Microchip_ATmega:ATmega1280-16C +MCU_Microchip_ATmega:ATmega1280V-8A +MCU_Microchip_ATmega:ATmega1280V-8C +MCU_Microchip_ATmega:ATmega1281-16A +MCU_Microchip_ATmega:ATmega1281-16M +MCU_Microchip_ATmega:ATmega1281V-8A +MCU_Microchip_ATmega:ATmega1281V-8M +MCU_Microchip_ATmega:ATmega1284-A +MCU_Microchip_ATmega:ATmega1284-M +MCU_Microchip_ATmega:ATmega1284-P +MCU_Microchip_ATmega:ATmega1284P-A +MCU_Microchip_ATmega:ATmega1284P-M +MCU_Microchip_ATmega:ATmega1284P-P +MCU_Microchip_ATmega:ATmega128A-A +MCU_Microchip_ATmega:ATmega128A-M +MCU_Microchip_ATmega:ATmega128L-8A +MCU_Microchip_ATmega:ATmega128L-8M +MCU_Microchip_ATmega:ATmega16-16A +MCU_Microchip_ATmega:ATmega16-16M +MCU_Microchip_ATmega:ATmega16-16P +MCU_Microchip_ATmega:ATmega162-16A +MCU_Microchip_ATmega:ATmega162-16M +MCU_Microchip_ATmega:ATmega162-16P +MCU_Microchip_ATmega:ATmega162V-8A +MCU_Microchip_ATmega:ATmega162V-8M +MCU_Microchip_ATmega:ATmega162V-8P +MCU_Microchip_ATmega:ATmega164A-A +MCU_Microchip_ATmega:ATmega164A-C +MCU_Microchip_ATmega:ATmega164A-M +MCU_Microchip_ATmega:ATmega164A-MC +MCU_Microchip_ATmega:ATmega164A-P +MCU_Microchip_ATmega:ATmega164P-20A +MCU_Microchip_ATmega:ATmega164P-20M +MCU_Microchip_ATmega:ATmega164P-20P +MCU_Microchip_ATmega:ATmega164PA-A +MCU_Microchip_ATmega:ATmega164PA-C +MCU_Microchip_ATmega:ATmega164PA-M +MCU_Microchip_ATmega:ATmega164PA-MC +MCU_Microchip_ATmega:ATmega164PA-P +MCU_Microchip_ATmega:ATmega164PV-10A +MCU_Microchip_ATmega:ATmega164PV-10M +MCU_Microchip_ATmega:ATmega164PV-10P +MCU_Microchip_ATmega:ATmega165A-A +MCU_Microchip_ATmega:ATmega165A-M +MCU_Microchip_ATmega:ATmega165P-16A +MCU_Microchip_ATmega:ATmega165P-16M +MCU_Microchip_ATmega:ATmega165PA-A +MCU_Microchip_ATmega:ATmega165PA-M +MCU_Microchip_ATmega:ATmega165PV-8A +MCU_Microchip_ATmega:ATmega165PV-8M +MCU_Microchip_ATmega:ATmega168-20A +MCU_Microchip_ATmega:ATmega168-20M +MCU_Microchip_ATmega:ATmega168-20P +MCU_Microchip_ATmega:ATmega168A-A +MCU_Microchip_ATmega:ATmega168A-CC +MCU_Microchip_ATmega:ATmega168A-M +MCU_Microchip_ATmega:ATmega168A-MM +MCU_Microchip_ATmega:ATmega168A-P +MCU_Microchip_ATmega:ATmega168P-20A +MCU_Microchip_ATmega:ATmega168P-20M +MCU_Microchip_ATmega:ATmega168P-20P +MCU_Microchip_ATmega:ATmega168PA-A +MCU_Microchip_ATmega:ATmega168PA-CC +MCU_Microchip_ATmega:ATmega168PA-M +MCU_Microchip_ATmega:ATmega168PA-MM +MCU_Microchip_ATmega:ATmega168PA-P +MCU_Microchip_ATmega:ATmega168PB-A +MCU_Microchip_ATmega:ATmega168PB-M +MCU_Microchip_ATmega:ATmega168PV-10A +MCU_Microchip_ATmega:ATmega168PV-10M +MCU_Microchip_ATmega:ATmega168PV-10P +MCU_Microchip_ATmega:ATmega168V-10A +MCU_Microchip_ATmega:ATmega168V-10M +MCU_Microchip_ATmega:ATmega168V-10P +MCU_Microchip_ATmega:ATmega169A-A +MCU_Microchip_ATmega:ATmega169A-M +MCU_Microchip_ATmega:ATmega169A-MC +MCU_Microchip_ATmega:ATmega169P-16A +MCU_Microchip_ATmega:ATmega169P-16M +MCU_Microchip_ATmega:ATmega169P-16MC +MCU_Microchip_ATmega:ATmega169PA-A +MCU_Microchip_ATmega:ATmega169PA-M +MCU_Microchip_ATmega:ATmega169PA-MC +MCU_Microchip_ATmega:ATmega169PV-8A +MCU_Microchip_ATmega:ATmega169PV-8M +MCU_Microchip_ATmega:ATmega169PV-8MC +MCU_Microchip_ATmega:ATmega16A-A +MCU_Microchip_ATmega:ATmega16A-M +MCU_Microchip_ATmega:ATmega16A-P +MCU_Microchip_ATmega:ATmega16L-8A +MCU_Microchip_ATmega:ATmega16L-8M +MCU_Microchip_ATmega:ATmega16L-8P +MCU_Microchip_ATmega:ATmega16M1-A +MCU_Microchip_ATmega:ATmega16M1-M +MCU_Microchip_ATmega:ATmega16U2-A +MCU_Microchip_ATmega:ATmega16U2-M +MCU_Microchip_ATmega:ATmega16U4-A +MCU_Microchip_ATmega:ATmega16U4-M +MCU_Microchip_ATmega:ATmega16U4RC-A +MCU_Microchip_ATmega:ATmega16U4RC-M +MCU_Microchip_ATmega:ATmega2560-16A +MCU_Microchip_ATmega:ATmega2560-16C +MCU_Microchip_ATmega:ATmega2560V-8A +MCU_Microchip_ATmega:ATmega2560V-8C +MCU_Microchip_ATmega:ATmega2561-16A +MCU_Microchip_ATmega:ATmega2561-16M +MCU_Microchip_ATmega:ATmega2561V-8A +MCU_Microchip_ATmega:ATmega2561V-8M +MCU_Microchip_ATmega:ATmega32-16A +MCU_Microchip_ATmega:ATmega32-16M +MCU_Microchip_ATmega:ATmega32-16P +MCU_Microchip_ATmega:ATmega3208-A +MCU_Microchip_ATmega:ATmega3208-M +MCU_Microchip_ATmega:ATmega3208-X +MCU_Microchip_ATmega:ATmega3209-A +MCU_Microchip_ATmega:ATmega3209-M +MCU_Microchip_ATmega:ATmega324A-A +MCU_Microchip_ATmega:ATmega324A-C +MCU_Microchip_ATmega:ATmega324A-M +MCU_Microchip_ATmega:ATmega324A-MC +MCU_Microchip_ATmega:ATmega324A-P +MCU_Microchip_ATmega:ATmega324P-20A +MCU_Microchip_ATmega:ATmega324P-20M +MCU_Microchip_ATmega:ATmega324P-20P +MCU_Microchip_ATmega:ATmega324PA-A +MCU_Microchip_ATmega:ATmega324PA-C +MCU_Microchip_ATmega:ATmega324PA-M +MCU_Microchip_ATmega:ATmega324PA-MC +MCU_Microchip_ATmega:ATmega324PA-P +MCU_Microchip_ATmega:ATmega324PB-A +MCU_Microchip_ATmega:ATmega324PB-M +MCU_Microchip_ATmega:ATmega324PV-10A +MCU_Microchip_ATmega:ATmega324PV-10M +MCU_Microchip_ATmega:ATmega324PV-10P +MCU_Microchip_ATmega:ATmega325-16A +MCU_Microchip_ATmega:ATmega325-16M +MCU_Microchip_ATmega:ATmega3250-16A +MCU_Microchip_ATmega:ATmega3250A-A +MCU_Microchip_ATmega:ATmega3250P-20A +MCU_Microchip_ATmega:ATmega3250PA-A +MCU_Microchip_ATmega:ATmega3250PV-10A +MCU_Microchip_ATmega:ATmega3250V-8A +MCU_Microchip_ATmega:ATmega325A-A +MCU_Microchip_ATmega:ATmega325A-M +MCU_Microchip_ATmega:ATmega325P-20A +MCU_Microchip_ATmega:ATmega325P-20M +MCU_Microchip_ATmega:ATmega325PA-A +MCU_Microchip_ATmega:ATmega325PA-M +MCU_Microchip_ATmega:ATmega325PV-10A +MCU_Microchip_ATmega:ATmega325PV-10M +MCU_Microchip_ATmega:ATmega325V-8A +MCU_Microchip_ATmega:ATmega325V-8M +MCU_Microchip_ATmega:ATmega328-A +MCU_Microchip_ATmega:ATmega328-M +MCU_Microchip_ATmega:ATmega328-MM +MCU_Microchip_ATmega:ATmega328-P +MCU_Microchip_ATmega:ATmega328P-A +MCU_Microchip_ATmega:ATmega328P-M +MCU_Microchip_ATmega:ATmega328P-MM +MCU_Microchip_ATmega:ATmega328P-P +MCU_Microchip_ATmega:ATmega328PB-A +MCU_Microchip_ATmega:ATmega328PB-M +MCU_Microchip_ATmega:ATmega329-16A +MCU_Microchip_ATmega:ATmega329-16M +MCU_Microchip_ATmega:ATmega3290-16A +MCU_Microchip_ATmega:ATmega3290A-A +MCU_Microchip_ATmega:ATmega3290P-20A +MCU_Microchip_ATmega:ATmega3290PA-A +MCU_Microchip_ATmega:ATmega3290PV-10A +MCU_Microchip_ATmega:ATmega3290V-8A +MCU_Microchip_ATmega:ATmega329A-A +MCU_Microchip_ATmega:ATmega329A-M +MCU_Microchip_ATmega:ATmega329P-20A +MCU_Microchip_ATmega:ATmega329P-20M +MCU_Microchip_ATmega:ATmega329PA-A +MCU_Microchip_ATmega:ATmega329PA-M +MCU_Microchip_ATmega:ATmega329PV-10A +MCU_Microchip_ATmega:ATmega329PV-10M +MCU_Microchip_ATmega:ATmega329V-8A +MCU_Microchip_ATmega:ATmega329V-8M +MCU_Microchip_ATmega:ATmega32A-A +MCU_Microchip_ATmega:ATmega32A-M +MCU_Microchip_ATmega:ATmega32A-P +MCU_Microchip_ATmega:ATmega32L-8A +MCU_Microchip_ATmega:ATmega32L-8M +MCU_Microchip_ATmega:ATmega32L-8P +MCU_Microchip_ATmega:ATmega32M1-A +MCU_Microchip_ATmega:ATmega32M1-M +MCU_Microchip_ATmega:ATmega32U2-A +MCU_Microchip_ATmega:ATmega32U2-M +MCU_Microchip_ATmega:ATmega32U4-A +MCU_Microchip_ATmega:ATmega32U4-M +MCU_Microchip_ATmega:ATmega32U4RC-A +MCU_Microchip_ATmega:ATmega32U4RC-M +MCU_Microchip_ATmega:ATmega406-1AA +MCU_Microchip_ATmega:ATmega48-20A +MCU_Microchip_ATmega:ATmega48-20M +MCU_Microchip_ATmega:ATmega48-20MM +MCU_Microchip_ATmega:ATmega48-20P +MCU_Microchip_ATmega:ATmega4808-A +MCU_Microchip_ATmega:ATmega4808-M +MCU_Microchip_ATmega:ATmega4808-X +MCU_Microchip_ATmega:ATmega4809-A +MCU_Microchip_ATmega:ATmega4809-M +MCU_Microchip_ATmega:ATmega48A-A +MCU_Microchip_ATmega:ATmega48A-CC +MCU_Microchip_ATmega:ATmega48A-M +MCU_Microchip_ATmega:ATmega48A-MM +MCU_Microchip_ATmega:ATmega48A-P +MCU_Microchip_ATmega:ATmega48P-20A +MCU_Microchip_ATmega:ATmega48P-20M +MCU_Microchip_ATmega:ATmega48P-20MM +MCU_Microchip_ATmega:ATmega48P-20P +MCU_Microchip_ATmega:ATmega48PA-A +MCU_Microchip_ATmega:ATmega48PA-CC +MCU_Microchip_ATmega:ATmega48PA-M +MCU_Microchip_ATmega:ATmega48PA-MM +MCU_Microchip_ATmega:ATmega48PA-P +MCU_Microchip_ATmega:ATmega48PB-A +MCU_Microchip_ATmega:ATmega48PB-M +MCU_Microchip_ATmega:ATmega48PV-10A +MCU_Microchip_ATmega:ATmega48PV-10M +MCU_Microchip_ATmega:ATmega48PV-10MM +MCU_Microchip_ATmega:ATmega48PV-10P +MCU_Microchip_ATmega:ATmega48V-10A +MCU_Microchip_ATmega:ATmega48V-10M +MCU_Microchip_ATmega:ATmega48V-10MM +MCU_Microchip_ATmega:ATmega48V-10P +MCU_Microchip_ATmega:ATmega64-16A +MCU_Microchip_ATmega:ATmega64-16M +MCU_Microchip_ATmega:ATmega640-16A +MCU_Microchip_ATmega:ATmega640-16C +MCU_Microchip_ATmega:ATmega640V-8A +MCU_Microchip_ATmega:ATmega640V-8C +MCU_Microchip_ATmega:ATmega644-20A +MCU_Microchip_ATmega:ATmega644-20M +MCU_Microchip_ATmega:ATmega644-20P +MCU_Microchip_ATmega:ATmega644A-A +MCU_Microchip_ATmega:ATmega644A-M +MCU_Microchip_ATmega:ATmega644A-P +MCU_Microchip_ATmega:ATmega644P-20A +MCU_Microchip_ATmega:ATmega644P-20M +MCU_Microchip_ATmega:ATmega644P-20P +MCU_Microchip_ATmega:ATmega644PA-A +MCU_Microchip_ATmega:ATmega644PA-M +MCU_Microchip_ATmega:ATmega644PA-P +MCU_Microchip_ATmega:ATmega644PV-10A +MCU_Microchip_ATmega:ATmega644PV-10M +MCU_Microchip_ATmega:ATmega644PV-10P +MCU_Microchip_ATmega:ATmega644V-10A +MCU_Microchip_ATmega:ATmega644V-10M +MCU_Microchip_ATmega:ATmega644V-10P +MCU_Microchip_ATmega:ATmega645-16A +MCU_Microchip_ATmega:ATmega645-16M +MCU_Microchip_ATmega:ATmega6450-16A +MCU_Microchip_ATmega:ATmega6450A-A +MCU_Microchip_ATmega:ATmega6450P-A +MCU_Microchip_ATmega:ATmega6450V-8A +MCU_Microchip_ATmega:ATmega645A-A +MCU_Microchip_ATmega:ATmega645A-M +MCU_Microchip_ATmega:ATmega645P-A +MCU_Microchip_ATmega:ATmega645P-M +MCU_Microchip_ATmega:ATmega645V-8A +MCU_Microchip_ATmega:ATmega645V-8M +MCU_Microchip_ATmega:ATmega649-16A +MCU_Microchip_ATmega:ATmega649-16M +MCU_Microchip_ATmega:ATmega6490-16A +MCU_Microchip_ATmega:ATmega6490A-A +MCU_Microchip_ATmega:ATmega6490P-A +MCU_Microchip_ATmega:ATmega6490V-8A +MCU_Microchip_ATmega:ATmega649A-A +MCU_Microchip_ATmega:ATmega649A-M +MCU_Microchip_ATmega:ATmega649P-A +MCU_Microchip_ATmega:ATmega649P-M +MCU_Microchip_ATmega:ATmega649V-8A +MCU_Microchip_ATmega:ATmega649V-8M +MCU_Microchip_ATmega:ATmega64A-A +MCU_Microchip_ATmega:ATmega64A-M +MCU_Microchip_ATmega:ATmega64L-8A +MCU_Microchip_ATmega:ATmega64L-8M +MCU_Microchip_ATmega:ATmega64M1-A +MCU_Microchip_ATmega:ATmega64M1-M +MCU_Microchip_ATmega:ATmega8-16A +MCU_Microchip_ATmega:ATmega8-16M +MCU_Microchip_ATmega:ATmega8-16P +MCU_Microchip_ATmega:ATmega8515-16A +MCU_Microchip_ATmega:ATmega8515-16J +MCU_Microchip_ATmega:ATmega8515-16M +MCU_Microchip_ATmega:ATmega8515-16P +MCU_Microchip_ATmega:ATmega8515L-8A +MCU_Microchip_ATmega:ATmega8515L-8J +MCU_Microchip_ATmega:ATmega8515L-8M +MCU_Microchip_ATmega:ATmega8515L-8P +MCU_Microchip_ATmega:ATmega8535-16A +MCU_Microchip_ATmega:ATmega8535-16J +MCU_Microchip_ATmega:ATmega8535-16M +MCU_Microchip_ATmega:ATmega8535-16P +MCU_Microchip_ATmega:ATmega8535L-8A +MCU_Microchip_ATmega:ATmega8535L-8J +MCU_Microchip_ATmega:ATmega8535L-8M +MCU_Microchip_ATmega:ATmega8535L-8P +MCU_Microchip_ATmega:ATmega88-20A +MCU_Microchip_ATmega:ATmega88-20M +MCU_Microchip_ATmega:ATmega88-20P +MCU_Microchip_ATmega:ATmega88A-A +MCU_Microchip_ATmega:ATmega88A-CC +MCU_Microchip_ATmega:ATmega88A-M +MCU_Microchip_ATmega:ATmega88A-MM +MCU_Microchip_ATmega:ATmega88A-P +MCU_Microchip_ATmega:ATmega88P-20A +MCU_Microchip_ATmega:ATmega88P-20M +MCU_Microchip_ATmega:ATmega88P-20P +MCU_Microchip_ATmega:ATmega88PA-A +MCU_Microchip_ATmega:ATmega88PA-CC +MCU_Microchip_ATmega:ATmega88PA-M +MCU_Microchip_ATmega:ATmega88PA-MM +MCU_Microchip_ATmega:ATmega88PA-P +MCU_Microchip_ATmega:ATmega88PB-A +MCU_Microchip_ATmega:ATmega88PB-M +MCU_Microchip_ATmega:ATmega88PV-10A +MCU_Microchip_ATmega:ATmega88PV-10M +MCU_Microchip_ATmega:ATmega88PV-10P +MCU_Microchip_ATmega:ATmega88V-10A +MCU_Microchip_ATmega:ATmega88V-10M +MCU_Microchip_ATmega:ATmega88V-10P +MCU_Microchip_ATmega:ATmega8A-A +MCU_Microchip_ATmega:ATmega8A-M +MCU_Microchip_ATmega:ATmega8A-P +MCU_Microchip_ATmega:ATmega8L-8A +MCU_Microchip_ATmega:ATmega8L-8M +MCU_Microchip_ATmega:ATmega8L-8P +MCU_Microchip_ATmega:ATmega8U2-A +MCU_Microchip_ATmega:ATmega8U2-M +MCU_Microchip_ATmega:ATxmega128A1-A +MCU_Microchip_ATmega:ATxmega128A1-C +MCU_Microchip_ATmega:ATxmega128A1-C7 +MCU_Microchip_ATmega:ATxmega128A1U-A +MCU_Microchip_ATmega:ATxmega128A1U-C +MCU_Microchip_ATmega:ATxmega128A1U-C7 +MCU_Microchip_ATmega:ATxmega128A3-A +MCU_Microchip_ATmega:ATxmega128A3-M +MCU_Microchip_ATmega:ATxmega128A3U-A +MCU_Microchip_ATmega:ATxmega128A3U-M +MCU_Microchip_ATmega:ATxmega128A4U-A +MCU_Microchip_ATmega:ATxmega128A4U-C +MCU_Microchip_ATmega:ATxmega128A4U-M +MCU_Microchip_ATmega:ATxmega128B1-A +MCU_Microchip_ATmega:ATxmega128B1-C +MCU_Microchip_ATmega:ATxmega128B3-A +MCU_Microchip_ATmega:ATxmega128B3-M +MCU_Microchip_ATmega:ATxmega128B3-MC +MCU_Microchip_ATmega:ATxmega128C3-A +MCU_Microchip_ATmega:ATxmega128C3-M +MCU_Microchip_ATmega:ATxmega128D3-A +MCU_Microchip_ATmega:ATxmega128D3-M +MCU_Microchip_ATmega:ATxmega128D4-A +MCU_Microchip_ATmega:ATxmega128D4-C +MCU_Microchip_ATmega:ATxmega128D4-M +MCU_Microchip_ATmega:ATxmega16A4U-A +MCU_Microchip_ATmega:ATxmega16A4U-C +MCU_Microchip_ATmega:ATxmega16A4U-M +MCU_Microchip_ATmega:ATxmega16C4-A +MCU_Microchip_ATmega:ATxmega16C4-C +MCU_Microchip_ATmega:ATxmega16C4-M +MCU_Microchip_ATmega:ATxmega16D4-A +MCU_Microchip_ATmega:ATxmega16D4-C +MCU_Microchip_ATmega:ATxmega16D4-M +MCU_Microchip_ATmega:ATxmega16E5-A +MCU_Microchip_ATmega:ATxmega16E5-M +MCU_Microchip_ATmega:ATxmega16E5-M4 +MCU_Microchip_ATmega:ATxmega192A3-A +MCU_Microchip_ATmega:ATxmega192A3-M +MCU_Microchip_ATmega:ATxmega192A3U-A +MCU_Microchip_ATmega:ATxmega192A3U-M +MCU_Microchip_ATmega:ATxmega192C3-A +MCU_Microchip_ATmega:ATxmega192C3-M +MCU_Microchip_ATmega:ATxmega192D3-A +MCU_Microchip_ATmega:ATxmega192D3-M +MCU_Microchip_ATmega:ATxmega256A3-A +MCU_Microchip_ATmega:ATxmega256A3-M +MCU_Microchip_ATmega:ATxmega256A3B-A +MCU_Microchip_ATmega:ATxmega256A3B-M +MCU_Microchip_ATmega:ATxmega256A3BU-A +MCU_Microchip_ATmega:ATxmega256A3BU-M +MCU_Microchip_ATmega:ATxmega256A3U-A +MCU_Microchip_ATmega:ATxmega256A3U-M +MCU_Microchip_ATmega:ATxmega256C3-A +MCU_Microchip_ATmega:ATxmega256C3-M +MCU_Microchip_ATmega:ATxmega256D3-A +MCU_Microchip_ATmega:ATxmega256D3-M +MCU_Microchip_ATmega:ATxmega32A4U-A +MCU_Microchip_ATmega:ATxmega32A4U-C +MCU_Microchip_ATmega:ATxmega32A4U-M +MCU_Microchip_ATmega:ATxmega32C3-A +MCU_Microchip_ATmega:ATxmega32C3-M +MCU_Microchip_ATmega:ATxmega32C4-A +MCU_Microchip_ATmega:ATxmega32C4-C +MCU_Microchip_ATmega:ATxmega32C4-M +MCU_Microchip_ATmega:ATxmega32D3-A +MCU_Microchip_ATmega:ATxmega32D3-M +MCU_Microchip_ATmega:ATxmega32D4-A +MCU_Microchip_ATmega:ATxmega32D4-C +MCU_Microchip_ATmega:ATxmega32D4-M +MCU_Microchip_ATmega:ATxmega32E5-A +MCU_Microchip_ATmega:ATxmega32E5-M +MCU_Microchip_ATmega:ATxmega32E5-M4 +MCU_Microchip_ATmega:ATxmega384C3-A +MCU_Microchip_ATmega:ATxmega384C3-M +MCU_Microchip_ATmega:ATxmega384D3-A +MCU_Microchip_ATmega:ATxmega384D3-M +MCU_Microchip_ATmega:ATxmega64A1-A +MCU_Microchip_ATmega:ATxmega64A1-C +MCU_Microchip_ATmega:ATxmega64A1-C7 +MCU_Microchip_ATmega:ATxmega64A1U-A +MCU_Microchip_ATmega:ATxmega64A1U-C +MCU_Microchip_ATmega:ATxmega64A1U-C7 +MCU_Microchip_ATmega:ATxmega64A3-A +MCU_Microchip_ATmega:ATxmega64A3-M +MCU_Microchip_ATmega:ATxmega64A3U-A +MCU_Microchip_ATmega:ATxmega64A3U-M +MCU_Microchip_ATmega:ATxmega64A4U-A +MCU_Microchip_ATmega:ATxmega64A4U-C +MCU_Microchip_ATmega:ATxmega64A4U-M +MCU_Microchip_ATmega:ATxmega64B1-A +MCU_Microchip_ATmega:ATxmega64B1-C +MCU_Microchip_ATmega:ATxmega64B3-A +MCU_Microchip_ATmega:ATxmega64B3-M +MCU_Microchip_ATmega:ATxmega64C3-A +MCU_Microchip_ATmega:ATxmega64C3-M +MCU_Microchip_ATmega:ATxmega64D3-A +MCU_Microchip_ATmega:ATxmega64D3-M +MCU_Microchip_ATmega:ATxmega64D4-A +MCU_Microchip_ATmega:ATxmega64D4-C +MCU_Microchip_ATmega:ATxmega64D4-M +MCU_Microchip_ATmega:ATxmega8E5-A +MCU_Microchip_ATmega:ATxmega8E5-M +MCU_Microchip_ATmega:ATxmega8E5-M4 +MCU_Microchip_ATtiny:ATtiny10-MA +MCU_Microchip_ATtiny:ATtiny10-TS +MCU_Microchip_ATtiny:ATtiny102-M +MCU_Microchip_ATtiny:ATtiny102-SS +MCU_Microchip_ATtiny:ATtiny104-SS +MCU_Microchip_ATtiny:ATtiny13-20M +MCU_Microchip_ATtiny:ATtiny13-20MM +MCU_Microchip_ATtiny:ATtiny13-20P +MCU_Microchip_ATtiny:ATtiny13-20S +MCU_Microchip_ATtiny:ATtiny13-20SS +MCU_Microchip_ATtiny:ATtiny13A-M +MCU_Microchip_ATtiny:ATtiny13A-MM +MCU_Microchip_ATtiny:ATtiny13A-P +MCU_Microchip_ATtiny:ATtiny13A-S +MCU_Microchip_ATtiny:ATtiny13A-SS +MCU_Microchip_ATtiny:ATtiny13V-10M +MCU_Microchip_ATtiny:ATtiny13V-10MM +MCU_Microchip_ATtiny:ATtiny13V-10P +MCU_Microchip_ATtiny:ATtiny13V-10S +MCU_Microchip_ATtiny:ATtiny13V-10SS +MCU_Microchip_ATtiny:ATtiny1604-SS +MCU_Microchip_ATtiny:ATtiny1606-M +MCU_Microchip_ATtiny:ATtiny1606-S +MCU_Microchip_ATtiny:ATtiny1607-M +MCU_Microchip_ATtiny:ATtiny1614-SS +MCU_Microchip_ATtiny:ATtiny1616-M +MCU_Microchip_ATtiny:ATtiny1616-S +MCU_Microchip_ATtiny:ATtiny1617-M +MCU_Microchip_ATtiny:ATtiny1624-SS +MCU_Microchip_ATtiny:ATtiny1624-X +MCU_Microchip_ATtiny:ATtiny1626-M +MCU_Microchip_ATtiny:ATtiny1626-S +MCU_Microchip_ATtiny:ATtiny1626-X +MCU_Microchip_ATtiny:ATtiny1627-M +MCU_Microchip_ATtiny:ATtiny1634-M +MCU_Microchip_ATtiny:ATtiny1634-S +MCU_Microchip_ATtiny:ATtiny167-M +MCU_Microchip_ATtiny:ATtiny167-S +MCU_Microchip_ATtiny:ATtiny167-X +MCU_Microchip_ATtiny:ATtiny20-CC +MCU_Microchip_ATtiny:ATtiny20-MM +MCU_Microchip_ATtiny:ATtiny20-SS +MCU_Microchip_ATtiny:ATtiny20-U +MCU_Microchip_ATtiny:ATtiny20-X +MCU_Microchip_ATtiny:ATtiny202-SS +MCU_Microchip_ATtiny:ATtiny204-SS +MCU_Microchip_ATtiny:ATtiny212-SS +MCU_Microchip_ATtiny:ATtiny214-SS +MCU_Microchip_ATtiny:ATtiny2313-20M +MCU_Microchip_ATtiny:ATtiny2313-20P +MCU_Microchip_ATtiny:ATtiny2313-20S +MCU_Microchip_ATtiny:ATtiny2313A-M +MCU_Microchip_ATtiny:ATtiny2313A-MM +MCU_Microchip_ATtiny:ATtiny2313A-P +MCU_Microchip_ATtiny:ATtiny2313A-S +MCU_Microchip_ATtiny:ATtiny2313V-10M +MCU_Microchip_ATtiny:ATtiny2313V-10P +MCU_Microchip_ATtiny:ATtiny2313V-10S +MCU_Microchip_ATtiny:ATtiny24-20M +MCU_Microchip_ATtiny:ATtiny24-20P +MCU_Microchip_ATtiny:ATtiny24-20SS +MCU_Microchip_ATtiny:ATtiny24A-CC +MCU_Microchip_ATtiny:ATtiny24A-M +MCU_Microchip_ATtiny:ATtiny24A-MM +MCU_Microchip_ATtiny:ATtiny24A-P +MCU_Microchip_ATtiny:ATtiny24A-SS +MCU_Microchip_ATtiny:ATtiny24V-10M +MCU_Microchip_ATtiny:ATtiny24V-10P +MCU_Microchip_ATtiny:ATtiny24V-10SS +MCU_Microchip_ATtiny:ATtiny25-20M +MCU_Microchip_ATtiny:ATtiny25-20P +MCU_Microchip_ATtiny:ATtiny25-20S +MCU_Microchip_ATtiny:ATtiny25-20SS +MCU_Microchip_ATtiny:ATtiny25V-10M +MCU_Microchip_ATtiny:ATtiny25V-10P +MCU_Microchip_ATtiny:ATtiny25V-10S +MCU_Microchip_ATtiny:ATtiny25V-10SS +MCU_Microchip_ATtiny:ATtiny26-16M +MCU_Microchip_ATtiny:ATtiny26-16P +MCU_Microchip_ATtiny:ATtiny26-16S +MCU_Microchip_ATtiny:ATtiny261A-M +MCU_Microchip_ATtiny:ATtiny261A-P +MCU_Microchip_ATtiny:ATtiny261A-S +MCU_Microchip_ATtiny:ATtiny261A-X +MCU_Microchip_ATtiny:ATtiny26L-8M +MCU_Microchip_ATtiny:ATtiny26L-8P +MCU_Microchip_ATtiny:ATtiny26L-8S +MCU_Microchip_ATtiny:ATtiny28L-4A +MCU_Microchip_ATtiny:ATtiny28L-4M +MCU_Microchip_ATtiny:ATtiny28L-4P +MCU_Microchip_ATtiny:ATtiny28V-1A +MCU_Microchip_ATtiny:ATtiny28V-1M +MCU_Microchip_ATtiny:ATtiny28V-1P +MCU_Microchip_ATtiny:ATtiny3216-M +MCU_Microchip_ATtiny:ATtiny3216-S +MCU_Microchip_ATtiny:ATtiny3217-M +MCU_Microchip_ATtiny:ATtiny3224-SS +MCU_Microchip_ATtiny:ATtiny3224-X +MCU_Microchip_ATtiny:ATtiny3226-M +MCU_Microchip_ATtiny:ATtiny3226-S +MCU_Microchip_ATtiny:ATtiny3226-X +MCU_Microchip_ATtiny:ATtiny3227-M +MCU_Microchip_ATtiny:ATtiny4-MA +MCU_Microchip_ATtiny:ATtiny4-TS +MCU_Microchip_ATtiny:ATtiny40-MM +MCU_Microchip_ATtiny:ATtiny40-S +MCU_Microchip_ATtiny:ATtiny40-X +MCU_Microchip_ATtiny:ATtiny402-SS +MCU_Microchip_ATtiny:ATtiny404-SS +MCU_Microchip_ATtiny:ATtiny406-M +MCU_Microchip_ATtiny:ATtiny406-S +MCU_Microchip_ATtiny:ATtiny412-SS +MCU_Microchip_ATtiny:ATtiny414-SS +MCU_Microchip_ATtiny:ATtiny416-M +MCU_Microchip_ATtiny:ATtiny416-S +MCU_Microchip_ATtiny:ATtiny417-M +MCU_Microchip_ATtiny:ATtiny424-SS +MCU_Microchip_ATtiny:ATtiny424-X +MCU_Microchip_ATtiny:ATtiny426-M +MCU_Microchip_ATtiny:ATtiny426-S +MCU_Microchip_ATtiny:ATtiny426-X +MCU_Microchip_ATtiny:ATtiny427-M +MCU_Microchip_ATtiny:ATtiny4313-M +MCU_Microchip_ATtiny:ATtiny4313-MM +MCU_Microchip_ATtiny:ATtiny4313-P +MCU_Microchip_ATtiny:ATtiny4313-S +MCU_Microchip_ATtiny:ATtiny43U-M +MCU_Microchip_ATtiny:ATtiny43U-S +MCU_Microchip_ATtiny:ATtiny44-20M +MCU_Microchip_ATtiny:ATtiny44-20P +MCU_Microchip_ATtiny:ATtiny44-20SS +MCU_Microchip_ATtiny:ATtiny441-M +MCU_Microchip_ATtiny:ATtiny441-MM +MCU_Microchip_ATtiny:ATtiny441-SS +MCU_Microchip_ATtiny:ATtiny44A-CC +MCU_Microchip_ATtiny:ATtiny44A-M +MCU_Microchip_ATtiny:ATtiny44A-MM +MCU_Microchip_ATtiny:ATtiny44A-P +MCU_Microchip_ATtiny:ATtiny44A-SS +MCU_Microchip_ATtiny:ATtiny44V-10M +MCU_Microchip_ATtiny:ATtiny44V-10P +MCU_Microchip_ATtiny:ATtiny44V-10SS +MCU_Microchip_ATtiny:ATtiny45-20M +MCU_Microchip_ATtiny:ATtiny45-20P +MCU_Microchip_ATtiny:ATtiny45-20S +MCU_Microchip_ATtiny:ATtiny45-20X +MCU_Microchip_ATtiny:ATtiny45V-10M +MCU_Microchip_ATtiny:ATtiny45V-10P +MCU_Microchip_ATtiny:ATtiny45V-10S +MCU_Microchip_ATtiny:ATtiny45V-10X +MCU_Microchip_ATtiny:ATtiny461-20M +MCU_Microchip_ATtiny:ATtiny461-20P +MCU_Microchip_ATtiny:ATtiny461-20S +MCU_Microchip_ATtiny:ATtiny461A-M +MCU_Microchip_ATtiny:ATtiny461A-P +MCU_Microchip_ATtiny:ATtiny461A-S +MCU_Microchip_ATtiny:ATtiny461A-X +MCU_Microchip_ATtiny:ATtiny461V-10M +MCU_Microchip_ATtiny:ATtiny461V-10P +MCU_Microchip_ATtiny:ATtiny461V-10S +MCU_Microchip_ATtiny:ATtiny48-A +MCU_Microchip_ATtiny:ATtiny48-CC +MCU_Microchip_ATtiny:ATtiny48-M +MCU_Microchip_ATtiny:ATtiny48-MM +MCU_Microchip_ATtiny:ATtiny48-P +MCU_Microchip_ATtiny:ATtiny5-MA +MCU_Microchip_ATtiny:ATtiny5-TS +MCU_Microchip_ATtiny:ATtiny804-SS +MCU_Microchip_ATtiny:ATtiny806-M +MCU_Microchip_ATtiny:ATtiny806-S +MCU_Microchip_ATtiny:ATtiny807-M +MCU_Microchip_ATtiny:ATtiny814-SS +MCU_Microchip_ATtiny:ATtiny816-M +MCU_Microchip_ATtiny:ATtiny816-S +MCU_Microchip_ATtiny:ATtiny817-M +MCU_Microchip_ATtiny:ATtiny824-SS +MCU_Microchip_ATtiny:ATtiny824-X +MCU_Microchip_ATtiny:ATtiny826-M +MCU_Microchip_ATtiny:ATtiny826-S +MCU_Microchip_ATtiny:ATtiny826-X +MCU_Microchip_ATtiny:ATtiny827-M +MCU_Microchip_ATtiny:ATtiny828-A +MCU_Microchip_ATtiny:ATtiny828-M +MCU_Microchip_ATtiny:ATtiny84-20M +MCU_Microchip_ATtiny:ATtiny84-20P +MCU_Microchip_ATtiny:ATtiny84-20SS +MCU_Microchip_ATtiny:ATtiny841-M +MCU_Microchip_ATtiny:ATtiny841-MM +MCU_Microchip_ATtiny:ATtiny841-SS +MCU_Microchip_ATtiny:ATtiny84A-CC +MCU_Microchip_ATtiny:ATtiny84A-M +MCU_Microchip_ATtiny:ATtiny84A-MM +MCU_Microchip_ATtiny:ATtiny84A-P +MCU_Microchip_ATtiny:ATtiny84A-SS +MCU_Microchip_ATtiny:ATtiny84V-10M +MCU_Microchip_ATtiny:ATtiny84V-10P +MCU_Microchip_ATtiny:ATtiny84V-10SS +MCU_Microchip_ATtiny:ATtiny85-20M +MCU_Microchip_ATtiny:ATtiny85-20P +MCU_Microchip_ATtiny:ATtiny85-20S +MCU_Microchip_ATtiny:ATtiny85V-10M +MCU_Microchip_ATtiny:ATtiny85V-10P +MCU_Microchip_ATtiny:ATtiny85V-10S +MCU_Microchip_ATtiny:ATtiny861-20M +MCU_Microchip_ATtiny:ATtiny861-20P +MCU_Microchip_ATtiny:ATtiny861-20S +MCU_Microchip_ATtiny:ATtiny861A-M +MCU_Microchip_ATtiny:ATtiny861A-P +MCU_Microchip_ATtiny:ATtiny861A-S +MCU_Microchip_ATtiny:ATtiny861A-X +MCU_Microchip_ATtiny:ATtiny861V-10M +MCU_Microchip_ATtiny:ATtiny861V-10P +MCU_Microchip_ATtiny:ATtiny861V-10S +MCU_Microchip_ATtiny:ATtiny87-M +MCU_Microchip_ATtiny:ATtiny87-S +MCU_Microchip_ATtiny:ATtiny87-X +MCU_Microchip_ATtiny:ATtiny88-A +MCU_Microchip_ATtiny:ATtiny88-CC +MCU_Microchip_ATtiny:ATtiny88-M +MCU_Microchip_ATtiny:ATtiny88-MM +MCU_Microchip_ATtiny:ATtiny88-P +MCU_Microchip_ATtiny:ATtiny9-MA +MCU_Microchip_ATtiny:ATtiny9-TS +MCU_Microchip_AVR:AT90CAN128-16A +MCU_Microchip_AVR:AT90CAN128-16M +MCU_Microchip_AVR:AT90CAN32-16A +MCU_Microchip_AVR:AT90CAN32-16M +MCU_Microchip_AVR:AT90CAN64-16A +MCU_Microchip_AVR:AT90CAN64-16M +MCU_Microchip_AVR:AT90PWM1-16M +MCU_Microchip_AVR:AT90PWM1-16S +MCU_Microchip_AVR:AT90USB1286-A +MCU_Microchip_AVR:AT90USB1286-M +MCU_Microchip_AVR:AT90USB1287-A +MCU_Microchip_AVR:AT90USB1287-M +MCU_Microchip_AVR:AT90USB162-16A +MCU_Microchip_AVR:AT90USB162-16M +MCU_Microchip_AVR:AT90USB646-A +MCU_Microchip_AVR:AT90USB646-M +MCU_Microchip_AVR:AT90USB647-A +MCU_Microchip_AVR:AT90USB647-M +MCU_Microchip_AVR:AT90USB82-16M +MCU_Microchip_AVR_Dx:AVR128DA28x-xSO +MCU_Microchip_AVR_Dx:AVR128DA28x-xSP +MCU_Microchip_AVR_Dx:AVR128DA28x-xSS +MCU_Microchip_AVR_Dx:AVR128DA32x-xPT +MCU_Microchip_AVR_Dx:AVR128DA32x-xRXB +MCU_Microchip_AVR_Dx:AVR128DA48x-x6LX +MCU_Microchip_AVR_Dx:AVR128DA48x-xPT +MCU_Microchip_AVR_Dx:AVR128DA64x-xMR +MCU_Microchip_AVR_Dx:AVR128DA64x-xPT +MCU_Microchip_AVR_Dx:AVR128DB28x-xSO +MCU_Microchip_AVR_Dx:AVR128DB28x-xSP +MCU_Microchip_AVR_Dx:AVR128DB28x-xSS +MCU_Microchip_AVR_Dx:AVR128DB32x-xPT +MCU_Microchip_AVR_Dx:AVR128DB32x-xRXB +MCU_Microchip_AVR_Dx:AVR128DB48x-x6LX +MCU_Microchip_AVR_Dx:AVR128DB48x-xPT +MCU_Microchip_AVR_Dx:AVR128DB64x-xMR +MCU_Microchip_AVR_Dx:AVR128DB64x-xPT +MCU_Microchip_AVR_Dx:AVR32DA28x-xSO +MCU_Microchip_AVR_Dx:AVR32DA28x-xSP +MCU_Microchip_AVR_Dx:AVR32DA28x-xSS +MCU_Microchip_AVR_Dx:AVR32DA32x-xPT +MCU_Microchip_AVR_Dx:AVR32DA32x-xRXB +MCU_Microchip_AVR_Dx:AVR32DA48x-x6LX +MCU_Microchip_AVR_Dx:AVR32DA48x-xPT +MCU_Microchip_AVR_Dx:AVR32DB28x-xSO +MCU_Microchip_AVR_Dx:AVR32DB28x-xSP +MCU_Microchip_AVR_Dx:AVR32DB28x-xSS +MCU_Microchip_AVR_Dx:AVR32DB32x-xPT +MCU_Microchip_AVR_Dx:AVR32DB32x-xRXB +MCU_Microchip_AVR_Dx:AVR32DB48x-x6LX +MCU_Microchip_AVR_Dx:AVR32DB48x-xPT +MCU_Microchip_AVR_Dx:AVR64DA28x-xSO +MCU_Microchip_AVR_Dx:AVR64DA28x-xSP +MCU_Microchip_AVR_Dx:AVR64DA28x-xSS +MCU_Microchip_AVR_Dx:AVR64DA32x-xPT +MCU_Microchip_AVR_Dx:AVR64DA32x-xRXB +MCU_Microchip_AVR_Dx:AVR64DA48x-x6LX +MCU_Microchip_AVR_Dx:AVR64DA48x-xPT +MCU_Microchip_AVR_Dx:AVR64DA64x-xMR +MCU_Microchip_AVR_Dx:AVR64DA64x-xPT +MCU_Microchip_AVR_Dx:AVR64DB28x-xSO +MCU_Microchip_AVR_Dx:AVR64DB28x-xSP +MCU_Microchip_AVR_Dx:AVR64DB28x-xSS +MCU_Microchip_AVR_Dx:AVR64DB32x-xPT +MCU_Microchip_AVR_Dx:AVR64DB32x-xRXB +MCU_Microchip_AVR_Dx:AVR64DB48x-x6LX +MCU_Microchip_AVR_Dx:AVR64DB48x-xPT +MCU_Microchip_AVR_Dx:AVR64DB64x-xMR +MCU_Microchip_AVR_Dx:AVR64DB64x-xPT +MCU_Microchip_PIC10:PIC10F200-IMC +MCU_Microchip_PIC10:PIC10F200-IOT +MCU_Microchip_PIC10:PIC10F200-IP +MCU_Microchip_PIC10:PIC10F202-IMC +MCU_Microchip_PIC10:PIC10F202-IOT +MCU_Microchip_PIC10:PIC10F202-IP +MCU_Microchip_PIC10:PIC10F204-IMC +MCU_Microchip_PIC10:PIC10F204-IOT +MCU_Microchip_PIC10:PIC10F204-IP +MCU_Microchip_PIC10:PIC10F206-IMC +MCU_Microchip_PIC10:PIC10F206-IOT +MCU_Microchip_PIC10:PIC10F206-IP +MCU_Microchip_PIC10:PIC10F220-IMC +MCU_Microchip_PIC10:PIC10F220-IOT +MCU_Microchip_PIC10:PIC10F220-IP +MCU_Microchip_PIC10:PIC10F222-IMC +MCU_Microchip_PIC10:PIC10F222-IOT +MCU_Microchip_PIC10:PIC10F222-IP +MCU_Microchip_PIC10:PIC10F320-IMC +MCU_Microchip_PIC10:PIC10F320-IOT +MCU_Microchip_PIC10:PIC10F320-IP +MCU_Microchip_PIC10:PIC10F322-IMC +MCU_Microchip_PIC10:PIC10F322-IOT +MCU_Microchip_PIC10:PIC10F322-IP +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 +MCU_Microchip_PIC16:PIC16F1454-IML +MCU_Microchip_PIC16:PIC16F1454-IP +MCU_Microchip_PIC16:PIC16F1454-ISL +MCU_Microchip_PIC16:PIC16F1454-ISS +MCU_Microchip_PIC16:PIC16F1454-IST +MCU_Microchip_PIC16:PIC16F1455-IML +MCU_Microchip_PIC16:PIC16F1455-IP +MCU_Microchip_PIC16:PIC16F1455-ISL +MCU_Microchip_PIC16:PIC16F1455-ISS +MCU_Microchip_PIC16:PIC16F1455-IST +MCU_Microchip_PIC16:PIC16F1459-IML +MCU_Microchip_PIC16:PIC16F1459-IP +MCU_Microchip_PIC16:PIC16F1459-ISO +MCU_Microchip_PIC16:PIC16F1459-ISS +MCU_Microchip_PIC16:PIC16F1459-IST +MCU_Microchip_PIC16:PIC16F1503-IMG +MCU_Microchip_PIC16:PIC16F1503-IP +MCU_Microchip_PIC16:PIC16F1503-ISL +MCU_Microchip_PIC16:PIC16F1503-IST +MCU_Microchip_PIC16:PIC16F1507-IML +MCU_Microchip_PIC16:PIC16F1507-IP +MCU_Microchip_PIC16:PIC16F1507-ISO +MCU_Microchip_PIC16:PIC16F1507-ISS +MCU_Microchip_PIC16:PIC16F1508-IML +MCU_Microchip_PIC16:PIC16F1508-IP +MCU_Microchip_PIC16:PIC16F1508-ISO +MCU_Microchip_PIC16:PIC16F1508-ISS +MCU_Microchip_PIC16:PIC16F1509-IML +MCU_Microchip_PIC16:PIC16F1509-IP +MCU_Microchip_PIC16:PIC16F1509-ISO +MCU_Microchip_PIC16:PIC16F1509-ISS +MCU_Microchip_PIC16:PIC16F1512-IMV +MCU_Microchip_PIC16:PIC16F1512-ISO +MCU_Microchip_PIC16:PIC16F1512-ISP +MCU_Microchip_PIC16:PIC16F1512-ISS +MCU_Microchip_PIC16:PIC16F1513-IMV +MCU_Microchip_PIC16:PIC16F1513-ISO +MCU_Microchip_PIC16:PIC16F1513-ISP +MCU_Microchip_PIC16:PIC16F1513-ISS +MCU_Microchip_PIC16:PIC16F1516-IMV +MCU_Microchip_PIC16:PIC16F1516-ISO +MCU_Microchip_PIC16:PIC16F1516-ISP +MCU_Microchip_PIC16:PIC16F1516-ISS +MCU_Microchip_PIC16:PIC16F1517-IMV +MCU_Microchip_PIC16:PIC16F1517-IP +MCU_Microchip_PIC16:PIC16F1517-IPT +MCU_Microchip_PIC16:PIC16F1518-IMV +MCU_Microchip_PIC16:PIC16F1518-ISO +MCU_Microchip_PIC16:PIC16F1518-ISP +MCU_Microchip_PIC16:PIC16F1518-ISS +MCU_Microchip_PIC16:PIC16F1519-IMV +MCU_Microchip_PIC16:PIC16F1519-IP +MCU_Microchip_PIC16:PIC16F1519-IPT +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 +MCU_Microchip_PIC16:PIC16F15356-xSP +MCU_Microchip_PIC16:PIC16F15356-xSS +MCU_Microchip_PIC16:PIC16F15375-xML +MCU_Microchip_PIC16:PIC16F15375-xMV +MCU_Microchip_PIC16:PIC16F15375-xP +MCU_Microchip_PIC16:PIC16F15375-xPT +MCU_Microchip_PIC16:PIC16F15376-xML +MCU_Microchip_PIC16:PIC16F15376-xMV +MCU_Microchip_PIC16:PIC16F15376-xP +MCU_Microchip_PIC16:PIC16F15376-xPT +MCU_Microchip_PIC16:PIC16F15385-xMV +MCU_Microchip_PIC16:PIC16F15385-xPT +MCU_Microchip_PIC16:PIC16F15386-xMV +MCU_Microchip_PIC16:PIC16F15386-xPT +MCU_Microchip_PIC16:PIC16F1619-xGZ +MCU_Microchip_PIC16:PIC16F1619-xML +MCU_Microchip_PIC16:PIC16F1619-xP +MCU_Microchip_PIC16:PIC16F1619-xSO +MCU_Microchip_PIC16:PIC16F1619-xSS +MCU_Microchip_PIC16:PIC16F1786-xML +MCU_Microchip_PIC16:PIC16F1786-xP +MCU_Microchip_PIC16:PIC16F1786-xSO +MCU_Microchip_PIC16:PIC16F1786-xSP +MCU_Microchip_PIC16:PIC16F1786-xSS +MCU_Microchip_PIC16:PIC16F1829-IML +MCU_Microchip_PIC16:PIC16F1829-IP +MCU_Microchip_PIC16:PIC16F1829-ISL +MCU_Microchip_PIC16:PIC16F1829-ISO +MCU_Microchip_PIC16:PIC16F1829-ISS +MCU_Microchip_PIC16:PIC16F1829-IST +MCU_Microchip_PIC16:PIC16F1829LIN-ESS +MCU_Microchip_PIC16:PIC16F18324-xSL +MCU_Microchip_PIC16:PIC16F18325-ISL +MCU_Microchip_PIC16:PIC16F18325-xGZ +MCU_Microchip_PIC16:PIC16F18325-xJQ +MCU_Microchip_PIC16:PIC16F18344-GZ +MCU_Microchip_PIC16:PIC16F18344-P +MCU_Microchip_PIC16:PIC16F18344-SO +MCU_Microchip_PIC16:PIC16F18344-SS +MCU_Microchip_PIC16:PIC16F18344-xSL +MCU_Microchip_PIC16:PIC16F18346-GZ +MCU_Microchip_PIC16:PIC16F18346-P +MCU_Microchip_PIC16:PIC16F18346-SO +MCU_Microchip_PIC16:PIC16F18346-SS_0 +MCU_Microchip_PIC16:PIC16F18854-xSO +MCU_Microchip_PIC16:PIC16F18855-xMV +MCU_Microchip_PIC16:PIC16F18855-xSO +MCU_Microchip_PIC16:PIC16F19195-x5LX +MCU_Microchip_PIC16:PIC16F19195-xMR +MCU_Microchip_PIC16:PIC16F19195-xPT +MCU_Microchip_PIC16:PIC16F19196-x5LX +MCU_Microchip_PIC16:PIC16F19196-xMR +MCU_Microchip_PIC16:PIC16F19196-xPT +MCU_Microchip_PIC16:PIC16F19197-x5LX +MCU_Microchip_PIC16:PIC16F19197-xMR +MCU_Microchip_PIC16:PIC16F19197-xPT +MCU_Microchip_PIC16:PIC16F1934-IML +MCU_Microchip_PIC16:PIC16F1934-IPT +MCU_Microchip_PIC16:PIC16F1937-IML +MCU_Microchip_PIC16:PIC16F1937-IPT +MCU_Microchip_PIC16:PIC16F1939-IML +MCU_Microchip_PIC16:PIC16F1939-IPT +MCU_Microchip_PIC16:PIC16F505-IMG +MCU_Microchip_PIC16:PIC16F505-IP +MCU_Microchip_PIC16:PIC16F505-ISL +MCU_Microchip_PIC16:PIC16F505-IST +MCU_Microchip_PIC16:PIC16F54-IP +MCU_Microchip_PIC16:PIC16F54-ISO +MCU_Microchip_PIC16:PIC16F54-ISS +MCU_Microchip_PIC16:PIC16F610-IML +MCU_Microchip_PIC16:PIC16F610-IP +MCU_Microchip_PIC16:PIC16F616-IML +MCU_Microchip_PIC16:PIC16F616-IP +MCU_Microchip_PIC16:PIC16F627-xxIP +MCU_Microchip_PIC16:PIC16F627-xxISO +MCU_Microchip_PIC16:PIC16F627-xxISS +MCU_Microchip_PIC16:PIC16F627A-IP +MCU_Microchip_PIC16:PIC16F627A-ISO +MCU_Microchip_PIC16:PIC16F627A-ISS +MCU_Microchip_PIC16:PIC16F628-xxIP +MCU_Microchip_PIC16:PIC16F628-xxISO +MCU_Microchip_PIC16:PIC16F628-xxISS +MCU_Microchip_PIC16:PIC16F628A-IP +MCU_Microchip_PIC16:PIC16F628A-ISO +MCU_Microchip_PIC16:PIC16F628A-ISS +MCU_Microchip_PIC16:PIC16F631-IP +MCU_Microchip_PIC16:PIC16F631-ISO +MCU_Microchip_PIC16:PIC16F631-ISS +MCU_Microchip_PIC16:PIC16F648A-IP +MCU_Microchip_PIC16:PIC16F648A-ISO +MCU_Microchip_PIC16:PIC16F648A-ISS +MCU_Microchip_PIC16:PIC16F677-IP +MCU_Microchip_PIC16:PIC16F677-ISO +MCU_Microchip_PIC16:PIC16F677-ISS +MCU_Microchip_PIC16:PIC16F684-IML +MCU_Microchip_PIC16:PIC16F684-IP +MCU_Microchip_PIC16:PIC16F685-IP +MCU_Microchip_PIC16:PIC16F685-ISO +MCU_Microchip_PIC16:PIC16F685-ISS +MCU_Microchip_PIC16:PIC16F687-IP +MCU_Microchip_PIC16:PIC16F687-ISO +MCU_Microchip_PIC16:PIC16F687-ISS +MCU_Microchip_PIC16:PIC16F689-IP +MCU_Microchip_PIC16:PIC16F689-ISO +MCU_Microchip_PIC16:PIC16F689-ISS +MCU_Microchip_PIC16:PIC16F690-IP +MCU_Microchip_PIC16:PIC16F690-ISO +MCU_Microchip_PIC16:PIC16F690-ISS +MCU_Microchip_PIC16:PIC16F716-IP +MCU_Microchip_PIC16:PIC16F716-ISO +MCU_Microchip_PIC16:PIC16F716-ISS +MCU_Microchip_PIC16:PIC16F73-IML +MCU_Microchip_PIC16:PIC16F73-ISO +MCU_Microchip_PIC16:PIC16F73-ISP +MCU_Microchip_PIC16:PIC16F73-ISS +MCU_Microchip_PIC16:PIC16F74-IP +MCU_Microchip_PIC16:PIC16F76-IML +MCU_Microchip_PIC16:PIC16F76-ISO +MCU_Microchip_PIC16:PIC16F76-ISP +MCU_Microchip_PIC16:PIC16F76-ISS +MCU_Microchip_PIC16:PIC16F77-IP +MCU_Microchip_PIC16:PIC16F818-IML +MCU_Microchip_PIC16:PIC16F818-IP +MCU_Microchip_PIC16:PIC16F818-ISO +MCU_Microchip_PIC16:PIC16F818-ISS +MCU_Microchip_PIC16:PIC16F819-IML +MCU_Microchip_PIC16:PIC16F819-IP +MCU_Microchip_PIC16:PIC16F819-ISO +MCU_Microchip_PIC16:PIC16F819-ISS +MCU_Microchip_PIC16:PIC16F83-XXP +MCU_Microchip_PIC16:PIC16F83-XXSO +MCU_Microchip_PIC16:PIC16F84-XXP +MCU_Microchip_PIC16:PIC16F84-XXSO +MCU_Microchip_PIC16:PIC16F84A-XXP +MCU_Microchip_PIC16:PIC16F84A-XXSO +MCU_Microchip_PIC16:PIC16F84A-XXSS +MCU_Microchip_PIC16:PIC16F870-ISO +MCU_Microchip_PIC16:PIC16F870-ISP +MCU_Microchip_PIC16:PIC16F870-ISS +MCU_Microchip_PIC16:PIC16F871-IL +MCU_Microchip_PIC16:PIC16F871-IP +MCU_Microchip_PIC16:PIC16F871-IPT +MCU_Microchip_PIC16:PIC16F873-XXISO +MCU_Microchip_PIC16:PIC16F873-XXISP +MCU_Microchip_PIC16:PIC16F874-XXIP +MCU_Microchip_PIC16:PIC16F874A-IP +MCU_Microchip_PIC16:PIC16F874A-IPT +MCU_Microchip_PIC16:PIC16F876-XXISO +MCU_Microchip_PIC16:PIC16F876-XXISP +MCU_Microchip_PIC16:PIC16F877-XXIP +MCU_Microchip_PIC16:PIC16F877A-IP +MCU_Microchip_PIC16:PIC16F877A-IPT +MCU_Microchip_PIC16:PIC16F88-IML +MCU_Microchip_PIC16:PIC16F88-IP +MCU_Microchip_PIC16:PIC16F882-IP +MCU_Microchip_PIC16:PIC16F883-IP +MCU_Microchip_PIC16:PIC16F884-IP +MCU_Microchip_PIC16:PIC16F886-IP +MCU_Microchip_PIC16:PIC16F887-IP +MCU_Microchip_PIC16:PIC16LF15356-xML +MCU_Microchip_PIC16:PIC16LF15356-xMV +MCU_Microchip_PIC16:PIC16LF15356-xSO +MCU_Microchip_PIC16:PIC16LF15356-xSP +MCU_Microchip_PIC16:PIC16LF15356-xSS +MCU_Microchip_PIC16:PIC16LF15375-xML +MCU_Microchip_PIC16:PIC16LF15375-xMV +MCU_Microchip_PIC16:PIC16LF15375-xP +MCU_Microchip_PIC16:PIC16LF15375-xPT +MCU_Microchip_PIC16:PIC16LF15376-xML +MCU_Microchip_PIC16:PIC16LF15376-xMV +MCU_Microchip_PIC16:PIC16LF15376-xP +MCU_Microchip_PIC16:PIC16LF15376-xPT +MCU_Microchip_PIC16:PIC16LF15385-xMV +MCU_Microchip_PIC16:PIC16LF15385-xPT +MCU_Microchip_PIC16:PIC16LF15386-xMV +MCU_Microchip_PIC16:PIC16LF15386-xPT +MCU_Microchip_PIC16:PIC16LF1786-xML +MCU_Microchip_PIC16:PIC16LF1786-xP +MCU_Microchip_PIC16:PIC16LF1786-xSO +MCU_Microchip_PIC16:PIC16LF1786-xSP +MCU_Microchip_PIC16:PIC16LF1786-xSS +MCU_Microchip_PIC16:PIC16LF18325-ISL +MCU_Microchip_PIC16:PIC16LF18325-xGZ +MCU_Microchip_PIC16:PIC16LF18325-xJQ +MCU_Microchip_PIC16:PIC16LF1904-IP +MCU_Microchip_PIC16:PIC16LF1907-IP +MCU_Microchip_PIC16:PIC16LF19195-x5LX +MCU_Microchip_PIC16:PIC16LF19195-xMR +MCU_Microchip_PIC16:PIC16LF19195-xPT +MCU_Microchip_PIC16:PIC16LF19196-x5LX +MCU_Microchip_PIC16:PIC16LF19196-xMR +MCU_Microchip_PIC16:PIC16LF19196-xPT +MCU_Microchip_PIC16:PIC16LF19197-x5LX +MCU_Microchip_PIC16:PIC16LF19197-xMR +MCU_Microchip_PIC16:PIC16LF19197-xPT +MCU_Microchip_PIC18:PIC18F1220-SO +MCU_Microchip_PIC18:PIC18F1320-SO +MCU_Microchip_PIC18:PIC18F13K50-EP +MCU_Microchip_PIC18:PIC18F13K50-ESO +MCU_Microchip_PIC18:PIC18F13K50-ESS +MCU_Microchip_PIC18:PIC18F14K50-EP +MCU_Microchip_PIC18:PIC18F14K50-ESO +MCU_Microchip_PIC18:PIC18F14K50-ESS +MCU_Microchip_PIC18:PIC18F2331-IML +MCU_Microchip_PIC18:PIC18F2331-ISO +MCU_Microchip_PIC18:PIC18F2331-ISP +MCU_Microchip_PIC18:PIC18F23K20_ISS +MCU_Microchip_PIC18:PIC18F23K22-xSO +MCU_Microchip_PIC18:PIC18F23K22-xSP +MCU_Microchip_PIC18:PIC18F2420-xML +MCU_Microchip_PIC18:PIC18F2420-xSP +MCU_Microchip_PIC18:PIC18F2431-IML +MCU_Microchip_PIC18:PIC18F2431-ISO +MCU_Microchip_PIC18:PIC18F2431-ISP +MCU_Microchip_PIC18:PIC18F2450-IML +MCU_Microchip_PIC18:PIC18F2450-ISO +MCU_Microchip_PIC18:PIC18F2450-ISP +MCU_Microchip_PIC18:PIC18F2455-ISO +MCU_Microchip_PIC18:PIC18F2455-ISP +MCU_Microchip_PIC18:PIC18F24K20_ISS +MCU_Microchip_PIC18:PIC18F24K22-xSO +MCU_Microchip_PIC18:PIC18F24K22-xSP +MCU_Microchip_PIC18:PIC18F24K50-xML +MCU_Microchip_PIC18:PIC18F24K50-xSO +MCU_Microchip_PIC18:PIC18F24K50-xSP +MCU_Microchip_PIC18:PIC18F24K50-xSS +MCU_Microchip_PIC18:PIC18F2520-xML +MCU_Microchip_PIC18:PIC18F2520-xSP +MCU_Microchip_PIC18:PIC18F2550-ISO +MCU_Microchip_PIC18:PIC18F2550-ISP +MCU_Microchip_PIC18:PIC18F25K20_ISS +MCU_Microchip_PIC18:PIC18F25K22-xSO +MCU_Microchip_PIC18:PIC18F25K22-xSP +MCU_Microchip_PIC18:PIC18F25K50-xML +MCU_Microchip_PIC18:PIC18F25K50-xSO +MCU_Microchip_PIC18:PIC18F25K50-xSP +MCU_Microchip_PIC18:PIC18F25K50-xSS +MCU_Microchip_PIC18:PIC18F25K80_IML +MCU_Microchip_PIC18:PIC18F25K80_ISS +MCU_Microchip_PIC18:PIC18F25K83-xSP +MCU_Microchip_PIC18:PIC18F26K20_ISS +MCU_Microchip_PIC18:PIC18F26K22-xSO +MCU_Microchip_PIC18:PIC18F26K22-xSP +MCU_Microchip_PIC18:PIC18F26K80_IML +MCU_Microchip_PIC18:PIC18F26K80_ISS +MCU_Microchip_PIC18:PIC18F26K83-xSP +MCU_Microchip_PIC18:PIC18F27J53_ISS +MCU_Microchip_PIC18:PIC18F4331-IML +MCU_Microchip_PIC18:PIC18F4331-IP +MCU_Microchip_PIC18:PIC18F4331-IPT +MCU_Microchip_PIC18:PIC18F442-IP +MCU_Microchip_PIC18:PIC18F442-IPT +MCU_Microchip_PIC18:PIC18F4420-xP +MCU_Microchip_PIC18:PIC18F4431-IML +MCU_Microchip_PIC18:PIC18F4431-IP +MCU_Microchip_PIC18:PIC18F4431-IPT +MCU_Microchip_PIC18:PIC18F4450-IML +MCU_Microchip_PIC18:PIC18F4450-IP +MCU_Microchip_PIC18:PIC18F4450-IPT +MCU_Microchip_PIC18:PIC18F4455-IML +MCU_Microchip_PIC18:PIC18F4455-IP +MCU_Microchip_PIC18:PIC18F4455-IPT +MCU_Microchip_PIC18:PIC18F4458-IML +MCU_Microchip_PIC18:PIC18F4458-IP +MCU_Microchip_PIC18:PIC18F4458-IPT +MCU_Microchip_PIC18:PIC18F448-IP +MCU_Microchip_PIC18:PIC18F4480-IP +MCU_Microchip_PIC18:PIC18F44J10-IP +MCU_Microchip_PIC18:PIC18F452-IP +MCU_Microchip_PIC18:PIC18F452-IPT +MCU_Microchip_PIC18:PIC18F4520-xP +MCU_Microchip_PIC18:PIC18F4550-IML +MCU_Microchip_PIC18:PIC18F4550-IP +MCU_Microchip_PIC18:PIC18F4550-IPT +MCU_Microchip_PIC18:PIC18F4553-IML +MCU_Microchip_PIC18:PIC18F4553-IP +MCU_Microchip_PIC18:PIC18F4553-IPT +MCU_Microchip_PIC18:PIC18F458-IP +MCU_Microchip_PIC18:PIC18F4580-IP +MCU_Microchip_PIC18:PIC18F45J10-IP +MCU_Microchip_PIC18:PIC18F45K50_QFP +MCU_Microchip_PIC18:PIC18F45K80-IML +MCU_Microchip_PIC18:PIC18F45K80-IPT +MCU_Microchip_PIC18:PIC18F46K22-xPT +MCU_Microchip_PIC18:PIC18F46K80-IML +MCU_Microchip_PIC18:PIC18F46K80-IPT +MCU_Microchip_PIC18:PIC18F66J60-IPT +MCU_Microchip_PIC18:PIC18F66J65-IPT +MCU_Microchip_PIC18:PIC18F67J60-IPT +MCU_Microchip_PIC18:PIC18F87K22-xPT +MCU_Microchip_PIC18:PIC18F96J60-IPF +MCU_Microchip_PIC18:PIC18F96J60-IPT +MCU_Microchip_PIC18:PIC18F96J65-IPF +MCU_Microchip_PIC18:PIC18F96J65-IPT +MCU_Microchip_PIC18:PIC18F97J60-IPF +MCU_Microchip_PIC18:PIC18F97J60-IPT +MCU_Microchip_PIC18:PIC18LF1220-SO +MCU_Microchip_PIC18:PIC18LF1320-SO +MCU_Microchip_PIC18:PIC18LF13K50-EP +MCU_Microchip_PIC18:PIC18LF13K50-ESO +MCU_Microchip_PIC18:PIC18LF13K50-ESS +MCU_Microchip_PIC18:PIC18LF14K50-EP +MCU_Microchip_PIC18:PIC18LF14K50-ESO +MCU_Microchip_PIC18:PIC18LF14K50-ESS +MCU_Microchip_PIC18:PIC18LF2331-IML +MCU_Microchip_PIC18:PIC18LF2331-ISO +MCU_Microchip_PIC18:PIC18LF2331-ISP +MCU_Microchip_PIC18:PIC18LF2431-IML +MCU_Microchip_PIC18:PIC18LF2431-ISO +MCU_Microchip_PIC18:PIC18LF2431-ISP +MCU_Microchip_PIC18:PIC18LF2450-IML +MCU_Microchip_PIC18:PIC18LF2450-ISO +MCU_Microchip_PIC18:PIC18LF2450-ISP +MCU_Microchip_PIC18:PIC18LF2455-ISO +MCU_Microchip_PIC18:PIC18LF2455-ISP +MCU_Microchip_PIC18:PIC18LF24K50-xML +MCU_Microchip_PIC18:PIC18LF24K50-xSO +MCU_Microchip_PIC18:PIC18LF24K50-xSP +MCU_Microchip_PIC18:PIC18LF24K50-xSS +MCU_Microchip_PIC18:PIC18LF2550-ISO +MCU_Microchip_PIC18:PIC18LF2550-ISP +MCU_Microchip_PIC18:PIC18LF25K50-xML +MCU_Microchip_PIC18:PIC18LF25K50-xSO +MCU_Microchip_PIC18:PIC18LF25K50-xSP +MCU_Microchip_PIC18:PIC18LF25K50-xSS +MCU_Microchip_PIC18:PIC18LF25K80_IML +MCU_Microchip_PIC18:PIC18LF25K80_ISS +MCU_Microchip_PIC18:PIC18LF25K83-xSP +MCU_Microchip_PIC18:PIC18LF26K80_IML +MCU_Microchip_PIC18:PIC18LF26K80_ISS +MCU_Microchip_PIC18:PIC18LF26K83-xSP +MCU_Microchip_PIC18:PIC18LF4331-IML +MCU_Microchip_PIC18:PIC18LF4331-IP +MCU_Microchip_PIC18:PIC18LF4331-IPT +MCU_Microchip_PIC18:PIC18LF442-IP +MCU_Microchip_PIC18:PIC18LF442-IPT +MCU_Microchip_PIC18:PIC18LF4431-IML +MCU_Microchip_PIC18:PIC18LF4431-IP +MCU_Microchip_PIC18:PIC18LF4431-IPT +MCU_Microchip_PIC18:PIC18LF4450-IML +MCU_Microchip_PIC18:PIC18LF4450-IP +MCU_Microchip_PIC18:PIC18LF4450-IPT +MCU_Microchip_PIC18:PIC18LF4455-IML +MCU_Microchip_PIC18:PIC18LF4455-IP +MCU_Microchip_PIC18:PIC18LF4455-IPT +MCU_Microchip_PIC18:PIC18LF4458-IML +MCU_Microchip_PIC18:PIC18LF4458-IP +MCU_Microchip_PIC18:PIC18LF4458-IPT +MCU_Microchip_PIC18:PIC18LF448-IP +MCU_Microchip_PIC18:PIC18LF4480-IP +MCU_Microchip_PIC18:PIC18LF44J10-IP +MCU_Microchip_PIC18:PIC18LF452-IP +MCU_Microchip_PIC18:PIC18LF452-IPT +MCU_Microchip_PIC18:PIC18LF4550-IML +MCU_Microchip_PIC18:PIC18LF4550-IP +MCU_Microchip_PIC18:PIC18LF4550-IPT +MCU_Microchip_PIC18:PIC18LF4553-IML +MCU_Microchip_PIC18:PIC18LF4553-IP +MCU_Microchip_PIC18:PIC18LF4553-IPT +MCU_Microchip_PIC18:PIC18LF458-IP +MCU_Microchip_PIC18:PIC18LF4580-IP +MCU_Microchip_PIC18:PIC18LF45J10-IP +MCU_Microchip_PIC18:PIC18LF45K50_QFP +MCU_Microchip_PIC18:PIC18LF45K80-IML +MCU_Microchip_PIC18:PIC18LF45K80-IPT +MCU_Microchip_PIC18:PIC18LF46K80-IML +MCU_Microchip_PIC18:PIC18LF46K80-IPT +MCU_Microchip_PIC24:PIC24FJ128GA306-xMR +MCU_Microchip_PIC24:PIC24FJ128GA306-xPT +MCU_Microchip_PIC24:PIC24FJ256DA210-xBG +MCU_Microchip_PIC24:PIC24FJ256DA210-xPT +MCU_Microchip_PIC24:PIC24FJ64GA306-xMR +MCU_Microchip_PIC24:PIC24FJ64GA306-xPT +MCU_Microchip_PIC24:PIC24FV32KA304-IPT +MCU_Microchip_PIC32:PIC32MK1024GPD100-xPT +MCU_Microchip_PIC32:PIC32MK1024GPE100-xPT +MCU_Microchip_PIC32:PIC32MM0064GPL028x-ML +MCU_Microchip_PIC32:PIC32MX110F016D-IPT +MCU_Microchip_PIC32:PIC32MX120F032D-IPT +MCU_Microchip_PIC32:PIC32MX130F064D-IPT +MCU_Microchip_PIC32:PIC32MX150F128D-IPT +MCU_Microchip_PIC32:PIC32MX170F256D-IPT +MCU_Microchip_PIC32:PIC32MX210F016D-IPT +MCU_Microchip_PIC32:PIC32MX220F032D-IPT +MCU_Microchip_PIC32:PIC32MX230F064D-IPT +MCU_Microchip_PIC32:PIC32MX250F128D-IPT +MCU_Microchip_PIC32:PIC32MX270F256D-IPT +MCU_Microchip_PIC32:PIC32MX575F256H +MCU_Microchip_PIC32:PIC32MX575F512H +MCU_Microchip_PIC32:PIC32MX795F512L-80x-PF +MCU_Microchip_PIC32:PIC32MX795F512L-80x-PT +MCU_Microchip_SAMA:ATSAMA5D21 +MCU_Microchip_SAMD:ATSAMD09C13A-SS +MCU_Microchip_SAMD:ATSAMD09D14A-M +MCU_Microchip_SAMD:ATSAMD10C13A-SS +MCU_Microchip_SAMD:ATSAMD10C14A-SS +MCU_Microchip_SAMD:ATSAMD10D13A-M +MCU_Microchip_SAMD:ATSAMD10D13A-SS +MCU_Microchip_SAMD:ATSAMD10D14A-M +MCU_Microchip_SAMD:ATSAMD10D14A-SS +MCU_Microchip_SAMD:ATSAMD10D14A-U +MCU_Microchip_SAMD:ATSAMD11C14A-SS +MCU_Microchip_SAMD:ATSAMD11D14A-M +MCU_Microchip_SAMD:ATSAMD11D14A-SS +MCU_Microchip_SAMD:ATSAMD11D14A-U +MCU_Microchip_SAMD:ATSAMD21E15A-A +MCU_Microchip_SAMD:ATSAMD21E15A-M +MCU_Microchip_SAMD:ATSAMD21E15B-A +MCU_Microchip_SAMD:ATSAMD21E15B-M +MCU_Microchip_SAMD:ATSAMD21E15L-A +MCU_Microchip_SAMD:ATSAMD21E15L-M +MCU_Microchip_SAMD:ATSAMD21E16A-A +MCU_Microchip_SAMD:ATSAMD21E16A-M +MCU_Microchip_SAMD:ATSAMD21E16B-A +MCU_Microchip_SAMD:ATSAMD21E16B-M +MCU_Microchip_SAMD:ATSAMD21E16L-A +MCU_Microchip_SAMD:ATSAMD21E16L-M +MCU_Microchip_SAMD:ATSAMD21E17A-A +MCU_Microchip_SAMD:ATSAMD21E17A-M +MCU_Microchip_SAMD:ATSAMD21E17D-A +MCU_Microchip_SAMD:ATSAMD21E17D-M +MCU_Microchip_SAMD:ATSAMD21E17L-A +MCU_Microchip_SAMD:ATSAMD21E17L-M +MCU_Microchip_SAMD:ATSAMD21E18A-A +MCU_Microchip_SAMD:ATSAMD21E18A-M +MCU_Microchip_SAMD:ATSAMD21G15A-A +MCU_Microchip_SAMD:ATSAMD21G15A-M +MCU_Microchip_SAMD:ATSAMD21G15B-A +MCU_Microchip_SAMD:ATSAMD21G15B-M +MCU_Microchip_SAMD:ATSAMD21G16A-A +MCU_Microchip_SAMD:ATSAMD21G16A-M +MCU_Microchip_SAMD:ATSAMD21G16B-A +MCU_Microchip_SAMD:ATSAMD21G16B-M +MCU_Microchip_SAMD:ATSAMD21G16L-M +MCU_Microchip_SAMD:ATSAMD21G17A-A +MCU_Microchip_SAMD:ATSAMD21G17A-M +MCU_Microchip_SAMD:ATSAMD21G17D-A +MCU_Microchip_SAMD:ATSAMD21G17D-M +MCU_Microchip_SAMD:ATSAMD21G17L-M +MCU_Microchip_SAMD:ATSAMD21G18A-A +MCU_Microchip_SAMD:ATSAMD21G18A-M +MCU_Microchip_SAMD:ATSAMD21J15A-A +MCU_Microchip_SAMD:ATSAMD21J15A-M +MCU_Microchip_SAMD:ATSAMD21J15B-A +MCU_Microchip_SAMD:ATSAMD21J15B-C +MCU_Microchip_SAMD:ATSAMD21J15B-M +MCU_Microchip_SAMD:ATSAMD21J16A-A +MCU_Microchip_SAMD:ATSAMD21J16A-M +MCU_Microchip_SAMD:ATSAMD21J16B-A +MCU_Microchip_SAMD:ATSAMD21J16B-C +MCU_Microchip_SAMD:ATSAMD21J16B-M +MCU_Microchip_SAMD:ATSAMD21J17A-A +MCU_Microchip_SAMD:ATSAMD21J17A-M +MCU_Microchip_SAMD:ATSAMD21J17D-A +MCU_Microchip_SAMD:ATSAMD21J17D-C +MCU_Microchip_SAMD:ATSAMD21J17D-M +MCU_Microchip_SAMD:ATSAMD21J18A-A +MCU_Microchip_SAMD:ATSAMD21J18A-M +MCU_Microchip_SAMD:ATSAMD51J18A-A +MCU_Microchip_SAMD:ATSAMD51J18A-M +MCU_Microchip_SAMD:ATSAMD51J19A-A +MCU_Microchip_SAMD:ATSAMD51J19A-M +MCU_Microchip_SAMD:ATSAMD51J20A-A +MCU_Microchip_SAMD:ATSAMD51J20A-M +MCU_Microchip_SAMD:ATSAMDA1E14B-A +MCU_Microchip_SAMD:ATSAMDA1E14B-M +MCU_Microchip_SAMD:ATSAMDA1E15B-A +MCU_Microchip_SAMD:ATSAMDA1E15B-M +MCU_Microchip_SAMD:ATSAMDA1E16B-A +MCU_Microchip_SAMD:ATSAMDA1E16B-M +MCU_Microchip_SAMD:ATSAMDA1G14B-A +MCU_Microchip_SAMD:ATSAMDA1G14B-M +MCU_Microchip_SAMD:ATSAMDA1G15B-A +MCU_Microchip_SAMD:ATSAMDA1G15B-M +MCU_Microchip_SAMD:ATSAMDA1G16B-A +MCU_Microchip_SAMD:ATSAMDA1G16B-M +MCU_Microchip_SAMD:ATSAMDA1J14B-A +MCU_Microchip_SAMD:ATSAMDA1J15B-A +MCU_Microchip_SAMD:ATSAMDA1J16B-A +MCU_Microchip_SAME:ATSAME51J18A-A +MCU_Microchip_SAME:ATSAME51J19A-A +MCU_Microchip_SAME:ATSAME51J20A-A +MCU_Microchip_SAME:ATSAME53J18A-M +MCU_Microchip_SAME:ATSAME53J19A-M +MCU_Microchip_SAME:ATSAME53J20A-M +MCU_Microchip_SAME:ATSAME54N19A-A +MCU_Microchip_SAME:ATSAME70J19A-AN +MCU_Microchip_SAME:ATSAME70J20A-AN +MCU_Microchip_SAME:ATSAME70J21A-AN +MCU_Microchip_SAME:ATSAME70N19A-AN +MCU_Microchip_SAME:ATSAME70N20A-AN +MCU_Microchip_SAME:ATSAME70N21A-AN +MCU_Microchip_SAME:ATSAME70Q19A-AN +MCU_Microchip_SAME:ATSAME70Q20A-AN +MCU_Microchip_SAME:ATSAME70Q21A-AN +MCU_Microchip_SAML:ATSAML21E15B-AUT +MCU_Microchip_SAML:ATSAML21E15B-MUT +MCU_Microchip_SAML:ATSAML21E16B-AUT +MCU_Microchip_SAML:ATSAML21E16B-MUT +MCU_Microchip_SAML:ATSAML21E17B-AUT +MCU_Microchip_SAML:ATSAML21E17B-MUT +MCU_Microchip_SAML:ATSAML21E18B-AUT +MCU_Microchip_SAML:ATSAML21E18B-MUT +MCU_Microchip_SAML:ATSAML21G16B-AUT +MCU_Microchip_SAML:ATSAML21G16B-MUT +MCU_Microchip_SAML:ATSAML21G17B-AUT +MCU_Microchip_SAML:ATSAML21G17B-MUT +MCU_Microchip_SAML:ATSAML21G18B-AUT +MCU_Microchip_SAML:ATSAML21G18B-MUT +MCU_Microchip_SAML:ATSAML21J16B-AUT +MCU_Microchip_SAML:ATSAML21J16B-MUT +MCU_Microchip_SAML:ATSAML21J17B-AUT +MCU_Microchip_SAML:ATSAML21J17B-MUT +MCU_Microchip_SAML:ATSAML21J18B-AUT +MCU_Microchip_SAML:ATSAML21J18B-MUT +MCU_Microchip_SAMV:ATSAMV71Q19B-A +MCU_Microchip_SAMV:ATSAMV71Q20B-A +MCU_Microchip_SAMV:ATSAMV71Q21B-A +MCU_Module:Adafruit_Feather_32u4_BluefruitLE +MCU_Module:Adafruit_Feather_Generic +MCU_Module:Adafruit_Feather_HUZZAH32_ESP32 +MCU_Module:Adafruit_Feather_HUZZAH_ESP8266 +MCU_Module:Adafruit_Feather_M0_Adalogger +MCU_Module:Adafruit_Feather_M0_Basic_Proto +MCU_Module:Adafruit_Feather_M0_BluefruitLE +MCU_Module:Adafruit_Feather_M0_Express +MCU_Module:Adafruit_Feather_M0_RFM69HCW_Packet_Radio +MCU_Module:Adafruit_Feather_M0_RFM9x_LoRa_Radio +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 +MCU_Module:Arduino_UNO_R3 +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 +MCU_Module:NUCLEO144-F413ZH +MCU_Module:NUCLEO144-F429ZI +MCU_Module:NUCLEO144-F439ZI +MCU_Module:NUCLEO144-F446ZE +MCU_Module:NUCLEO144-F722ZE +MCU_Module:NUCLEO144-F746ZG +MCU_Module:NUCLEO144-F756ZG +MCU_Module:NUCLEO144-F767ZI +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+ +MCU_Module:PocketBeagle +MCU_Module:RaspberryPi-CM1 +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_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 +MCU_NXP_ColdFire:MCF5282 +MCU_NXP_ColdFire:MCF5328-BGA256 +MCU_NXP_ColdFire:MCF5407 +MCU_NXP_HC11:68HC11 +MCU_NXP_HC11:68HC11A8 +MCU_NXP_HC11:68HC11F1 +MCU_NXP_HC11:68HC11_PLCC +MCU_NXP_HC11:68HC711_PLCC +MCU_NXP_HC11:MC68HC11A0CC +MCU_NXP_HC11:MC68HC11A1CC +MCU_NXP_HC11:MC68HC11A7CC +MCU_NXP_HC11:MC68HC11A8CC +MCU_NXP_HC11:MC68HC11F1CC +MCU_NXP_HC12:MC68HC812A4 +MCU_NXP_HC12:MC68HC912 +MCU_NXP_HCS12:MC9S12DT256 +MCU_NXP_Kinetis:MK20DN128VFM5 +MCU_NXP_Kinetis:MK20DN32VFM5 +MCU_NXP_Kinetis:MK20DN64VFM5 +MCU_NXP_Kinetis:MK20DX128VFM5 +MCU_NXP_Kinetis:MK20DX32VFM5 +MCU_NXP_Kinetis:MK20DX64VFM5 +MCU_NXP_Kinetis:MK20FN1M0VMD12 +MCU_NXP_Kinetis:MK20FX512VMD12 +MCU_NXP_Kinetis:MK26FN2M0VMD18 +MCU_NXP_Kinetis:MKE02Z16VLC4 +MCU_NXP_Kinetis:MKE02Z16VLD4 +MCU_NXP_Kinetis:MKE02Z32VLC4 +MCU_NXP_Kinetis:MKE02Z32VLD4 +MCU_NXP_Kinetis:MKE02Z32VLH4 +MCU_NXP_Kinetis:MKE02Z32VQH4 +MCU_NXP_Kinetis:MKE02Z64VLC4 +MCU_NXP_Kinetis:MKE02Z64VLD4 +MCU_NXP_Kinetis:MKE02Z64VLH4 +MCU_NXP_Kinetis:MKE02Z64VQH4 +MCU_NXP_Kinetis:MKE16Z64VLF4 +MCU_NXP_Kinetis:MKL02Z16VFG4 +MCU_NXP_Kinetis:MKL02Z16VFK4 +MCU_NXP_Kinetis:MKL02Z16VFM4 +MCU_NXP_Kinetis:MKL02Z32CAF4 +MCU_NXP_Kinetis:MKL02Z32VFG4 +MCU_NXP_Kinetis:MKL02Z32VFK4 +MCU_NXP_Kinetis:MKL02Z32VFM4 +MCU_NXP_Kinetis:MKL02Z8VFG4 +MCU_NXP_Kinetis:MKL03Z16VFG4 +MCU_NXP_Kinetis:MKL03Z16VFK4 +MCU_NXP_Kinetis:MKL03Z32CAF4 +MCU_NXP_Kinetis:MKL03Z32CBF4 +MCU_NXP_Kinetis:MKL03Z32VFG4 +MCU_NXP_Kinetis:MKL03Z32VFK4 +MCU_NXP_Kinetis:MKL03Z8VFG4 +MCU_NXP_Kinetis:MKL03Z8VFK4 +MCU_NXP_Kinetis:MKL04Z16VFK4 +MCU_NXP_Kinetis:MKL04Z16VFM4 +MCU_NXP_Kinetis:MKL04Z16VLC4 +MCU_NXP_Kinetis:MKL04Z16VLF4 +MCU_NXP_Kinetis:MKL04Z32VFK4 +MCU_NXP_Kinetis:MKL04Z32VFM4 +MCU_NXP_Kinetis:MKL04Z32VLC4 +MCU_NXP_Kinetis:MKL04Z32VLF4 +MCU_NXP_Kinetis:MKL04Z8VFK4 +MCU_NXP_Kinetis:MKL04Z8VFM4 +MCU_NXP_Kinetis:MKL04Z8VLC4 +MCU_NXP_Kinetis:MKL05Z16VFK4 +MCU_NXP_Kinetis:MKL05Z16VFM4 +MCU_NXP_Kinetis:MKL05Z16VLC4 +MCU_NXP_Kinetis:MKL05Z16VLF4 +MCU_NXP_Kinetis:MKL05Z32VFK4 +MCU_NXP_Kinetis:MKL05Z32VFM4 +MCU_NXP_Kinetis:MKL05Z32VLC4 +MCU_NXP_Kinetis:MKL05Z32VLF4 +MCU_NXP_Kinetis:MKL05Z8VFK4 +MCU_NXP_Kinetis:MKL05Z8VFM4 +MCU_NXP_Kinetis:MKL05Z8VLC4 +MCU_NXP_Kinetis:MKL16Z128VFM4 +MCU_NXP_Kinetis:MKL16Z128VFT4 +MCU_NXP_Kinetis:MKL16Z128VLH4 +MCU_NXP_Kinetis:MKL16Z256VLH4 +MCU_NXP_Kinetis:MKL16Z256VMP4 +MCU_NXP_Kinetis:MKL16Z32VFM4 +MCU_NXP_Kinetis:MKL16Z32VFT4 +MCU_NXP_Kinetis:MKL16Z32VLH4 +MCU_NXP_Kinetis:MKL16Z64VFM4 +MCU_NXP_Kinetis:MKL16Z64VFT4 +MCU_NXP_Kinetis:MKL16Z64VLH4 +MCU_NXP_Kinetis:MKL17Z128VFM4 +MCU_NXP_Kinetis:MKL17Z128VFT4 +MCU_NXP_Kinetis:MKL17Z128VLH4 +MCU_NXP_Kinetis:MKL17Z128VMP4 +MCU_NXP_Kinetis:MKL17Z256CAL4 +MCU_NXP_Kinetis:MKL17Z256VFM4 +MCU_NXP_Kinetis:MKL17Z256VFT4 +MCU_NXP_Kinetis:MKL17Z256VLH4 +MCU_NXP_Kinetis:MKL17Z256VMP4 +MCU_NXP_Kinetis:MKL17Z32VDA4 +MCU_NXP_Kinetis:MKL17Z32VFM4 +MCU_NXP_Kinetis:MKL17Z32VFT4 +MCU_NXP_Kinetis:MKL17Z32VLH4 +MCU_NXP_Kinetis:MKL17Z32VMP4 +MCU_NXP_Kinetis:MKL17Z64VDA4 +MCU_NXP_Kinetis:MKL17Z64VFM4 +MCU_NXP_Kinetis:MKL17Z64VFT4 +MCU_NXP_Kinetis:MKL17Z64VLH4 +MCU_NXP_Kinetis:MKL17Z64VMP4 +MCU_NXP_Kinetis:MKL24Z32VFM4 +MCU_NXP_Kinetis:MKL24Z32VFT4 +MCU_NXP_Kinetis:MKL24Z32VLH4 +MCU_NXP_Kinetis:MKL24Z32VLK4 +MCU_NXP_Kinetis:MKL24Z64VFM4 +MCU_NXP_Kinetis:MKL24Z64VFT4 +MCU_NXP_Kinetis:MKL24Z64VLH4 +MCU_NXP_Kinetis:MKL24Z64VLK4 +MCU_NXP_Kinetis:MKL25Z128VFM4 +MCU_NXP_Kinetis:MKL25Z128VFT4 +MCU_NXP_Kinetis:MKL25Z128VLH4 +MCU_NXP_Kinetis:MKL25Z128VLK4 +MCU_NXP_Kinetis:MKL25Z32VFM4 +MCU_NXP_Kinetis:MKL25Z32VFT4 +MCU_NXP_Kinetis:MKL25Z32VLH4 +MCU_NXP_Kinetis:MKL25Z32VLK4 +MCU_NXP_Kinetis:MKL25Z64VFM4 +MCU_NXP_Kinetis:MKL25Z64VFT4 +MCU_NXP_Kinetis:MKL25Z64VLH4 +MCU_NXP_Kinetis:MKL25Z64VLK4 +MCU_NXP_Kinetis:MKL26Z128CAL4 +MCU_NXP_Kinetis:MKL26Z128VFM4 +MCU_NXP_Kinetis:MKL26Z128VFT4 +MCU_NXP_Kinetis:MKL26Z128VLH4 +MCU_NXP_Kinetis:MKL26Z128VLL4 +MCU_NXP_Kinetis:MKL26Z128VMC4 +MCU_NXP_Kinetis:MKL26Z256VLH4 +MCU_NXP_Kinetis:MKL26Z256VLL4 +MCU_NXP_Kinetis:MKL26Z256VMC4 +MCU_NXP_Kinetis:MKL26Z256VMP4 +MCU_NXP_Kinetis:MKL26Z32VFM4 +MCU_NXP_Kinetis:MKL26Z32VFT4 +MCU_NXP_Kinetis:MKL26Z32VLH4 +MCU_NXP_Kinetis:MKL26Z64VFM4 +MCU_NXP_Kinetis:MKL26Z64VFT4 +MCU_NXP_Kinetis:MKL26Z64VLH4 +MCU_NXP_Kinetis:MKL27Z128VFT4 +MCU_NXP_Kinetis:MKL27Z128VLH4 +MCU_NXP_Kinetis:MKL27Z256VFT4 +MCU_NXP_Kinetis:MKL27Z256VLH4 +MCU_NXP_Kinetis:MKL27Z32VFM4 +MCU_NXP_Kinetis:MKL27Z32VFT4 +MCU_NXP_Kinetis:MKL27Z32VLH4 +MCU_NXP_Kinetis:MKL27Z64VFM4 +MCU_NXP_Kinetis:MKL27Z64VFT4 +MCU_NXP_Kinetis:MKL27Z64VLH4 +MCU_NXP_Kinetis:MKL28Z512VDC7 +MCU_NXP_Kinetis:MKL28Z512VLL7 +MCU_NXP_Kinetis:MKL43Z128VLH4 +MCU_NXP_Kinetis:MKL43Z128VMP4 +MCU_NXP_Kinetis:MKL43Z256VLH4 +MCU_NXP_Kinetis:MKL43Z256VMP4 +MCU_NXP_Kinetis:MKL46Z128VLH4 +MCU_NXP_Kinetis:MKL46Z128VLL4 +MCU_NXP_Kinetis:MKL46Z128VMC4 +MCU_NXP_Kinetis:MKL46Z256VLH4 +MCU_NXP_Kinetis:MKL46Z256VLL4 +MCU_NXP_Kinetis:MKL46Z256VMC4 +MCU_NXP_Kinetis:MKL46Z256VMP4 +MCU_NXP_Kinetis:MKV11Z128VLF7 +MCU_NXP_Kinetis:MKV11Z128VLH7 +MCU_NXP_Kinetis:MKW21Z256VHT +MCU_NXP_Kinetis:MKW21Z512VHT +MCU_NXP_Kinetis:MKW31Z256VHT +MCU_NXP_Kinetis:MKW31Z512VHT +MCU_NXP_Kinetis:MKW41Z256VHT +MCU_NXP_Kinetis:MKW41Z512VHT +MCU_NXP_LPC:LPC1102UK +MCU_NXP_LPC:LPC1104UK +MCU_NXP_LPC:LPC1111FHN33-101 +MCU_NXP_LPC:LPC1111FHN33-102 +MCU_NXP_LPC:LPC1111FHN33-103 +MCU_NXP_LPC:LPC1111FHN33-201 +MCU_NXP_LPC:LPC1111FHN33-202 +MCU_NXP_LPC:LPC1111FHN33-203 +MCU_NXP_LPC:LPC1111JHN33-103 +MCU_NXP_LPC:LPC1111JHN33-203 +MCU_NXP_LPC:LPC1112FHI33-102 +MCU_NXP_LPC:LPC1112FHI33-202 +MCU_NXP_LPC:LPC1112FHI33-203 +MCU_NXP_LPC:LPC1112FHN33-101 +MCU_NXP_LPC:LPC1112FHN33-102 +MCU_NXP_LPC:LPC1112FHN33-103 +MCU_NXP_LPC:LPC1112FHN33-201 +MCU_NXP_LPC:LPC1112FHN33-202 +MCU_NXP_LPC:LPC1112FHN33-203 +MCU_NXP_LPC:LPC1112JHI33-203 +MCU_NXP_LPC:LPC1112JHN33-103 +MCU_NXP_LPC:LPC1112JHN33-203 +MCU_NXP_LPC:LPC1113FBD48-301 +MCU_NXP_LPC:LPC1113FBD48-302 +MCU_NXP_LPC:LPC1113FBD48-303 +MCU_NXP_LPC:LPC1113FHN33-201 +MCU_NXP_LPC:LPC1113FHN33-202 +MCU_NXP_LPC:LPC1113FHN33-203 +MCU_NXP_LPC:LPC1113FHN33-301 +MCU_NXP_LPC:LPC1113FHN33-302 +MCU_NXP_LPC:LPC1113FHN33-303 +MCU_NXP_LPC:LPC1113JBD48-303 +MCU_NXP_LPC:LPC1113JHN33-203 +MCU_NXP_LPC:LPC1113JHN33-303 +MCU_NXP_LPC:LPC1114FBD48-301 +MCU_NXP_LPC:LPC1114FBD48-302 +MCU_NXP_LPC:LPC1114FBD48-303 +MCU_NXP_LPC:LPC1114FBD48-323 +MCU_NXP_LPC:LPC1114FBD48-333 +MCU_NXP_LPC:LPC1114FHI33-302 +MCU_NXP_LPC:LPC1114FHI33-303 +MCU_NXP_LPC:LPC1114FHN33-201 +MCU_NXP_LPC:LPC1114FHN33-202 +MCU_NXP_LPC:LPC1114FHN33-203 +MCU_NXP_LPC:LPC1114FHN33-301 +MCU_NXP_LPC:LPC1114FHN33-302 +MCU_NXP_LPC:LPC1114FHN33-303 +MCU_NXP_LPC:LPC1114FHN33-333 +MCU_NXP_LPC:LPC1114JBD48-303 +MCU_NXP_LPC:LPC1114JBD48-323 +MCU_NXP_LPC:LPC1114JBD48-333 +MCU_NXP_LPC:LPC1114JHI33-303 +MCU_NXP_LPC:LPC1114JHN33-203 +MCU_NXP_LPC:LPC1115FBD48-303 +MCU_NXP_LPC:LPC1115JBD48-303 +MCU_NXP_LPC:LPC11E12FBD48-201 +MCU_NXP_LPC:LPC11E13FBD48-301 +MCU_NXP_LPC:LPC11E14FBD48-401 +MCU_NXP_LPC:LPC11U12FBD48-201 +MCU_NXP_LPC:LPC11U13FBD48-201 +MCU_NXP_LPC:LPC11U14FBD48-201 +MCU_NXP_LPC:LPC11U22FBD48-301 +MCU_NXP_LPC:LPC11U23FBD48-301 +MCU_NXP_LPC:LPC11U24FBD48-301 +MCU_NXP_LPC:LPC11U24FBD48-401 +MCU_NXP_LPC:LPC11U34FBD48-311 +MCU_NXP_LPC:LPC11U34FBD48-421 +MCU_NXP_LPC:LPC11U35FBD48-401 +MCU_NXP_LPC:LPC11U36FBD48-401 +MCU_NXP_LPC:LPC11U37FBD48-401_ +MCU_NXP_LPC:LPC1224FBD48-101 +MCU_NXP_LPC:LPC1224FBD48-121 +MCU_NXP_LPC:LPC1224FBD64-101 +MCU_NXP_LPC:LPC1224FBD64-121 +MCU_NXP_LPC:LPC1225FBD48-301 +MCU_NXP_LPC:LPC1225FBD48-321 +MCU_NXP_LPC:LPC1225FBD64-301 +MCU_NXP_LPC:LPC1225FBD64-321 +MCU_NXP_LPC:LPC1226FBD48-301 +MCU_NXP_LPC:LPC1226FBD64-301 +MCU_NXP_LPC:LPC1227FBD48-301 +MCU_NXP_LPC:LPC1227FBD64-301 +MCU_NXP_LPC:LPC1763FBD100 +MCU_NXP_LPC:LPC1764FBD100 +MCU_NXP_LPC:LPC1765FBD100 +MCU_NXP_LPC:LPC1766FBD100 +MCU_NXP_LPC:LPC1767FBD100 +MCU_NXP_LPC:LPC1768FBD100 +MCU_NXP_LPC:LPC1769FBD100 +MCU_NXP_LPC:LPC2141FBD64 +MCU_NXP_LPC:LPC2142FBD64 +MCU_NXP_LPC:LPC2144FBD64 +MCU_NXP_LPC:LPC2146FBD64 +MCU_NXP_LPC:LPC2148FBD64 +MCU_NXP_LPC:LPC811M001JDH16 +MCU_NXP_LPC:LPC812M001JDH16 +MCU_NXP_LPC:LPC812M101JD20 +MCU_NXP_LPC:LPC812M101JDH20 +MCU_NXP_LPC:LPC812M101JTB16 +MCU_NXP_LPC:LPC822M101JDH20 +MCU_NXP_LPC:LPC822M101JHI33 +MCU_NXP_LPC:LPC824M201JDH20 +MCU_NXP_LPC:LPC824M201JHI33 +MCU_NXP_LPC:LPC832M101FDH20 +MCU_NXP_LPC:LPC834M101FHI33 +MCU_NXP_MAC7100:MAC7101 +MCU_NXP_MAC7100:MAC7111 +MCU_NXP_MCore:MMC2114CFCPU +MCU_NXP_NTAG:NHS3100 +MCU_NXP_S08:MC9S08AC128xFDE +MCU_NXP_S08:MC9S08AC128xFGE +MCU_NXP_S08:MC9S08AC128xFUE +MCU_NXP_S08:MC9S08AC128xLKE +MCU_NXP_S08:MC9S08AC128xPUE +MCU_NXP_S08:MC9S08AC16xFDE +MCU_NXP_S08:MC9S08AC16xFGE +MCU_NXP_S08:MC9S08AC16xFJE +MCU_NXP_S08:MC9S08AC32xFDE +MCU_NXP_S08:MC9S08AC32xFGE +MCU_NXP_S08:MC9S08AC32xFJE +MCU_NXP_S08:MC9S08AC32xFUE +MCU_NXP_S08:MC9S08AC32xPUE +MCU_NXP_S08:MC9S08AC48xFDE +MCU_NXP_S08:MC9S08AC48xFGE +MCU_NXP_S08:MC9S08AC48xFJE +MCU_NXP_S08:MC9S08AC48xFUE +MCU_NXP_S08:MC9S08AC48xPUE +MCU_NXP_S08:MC9S08AC60xFDE +MCU_NXP_S08:MC9S08AC60xFGE +MCU_NXP_S08:MC9S08AC60xFJE +MCU_NXP_S08:MC9S08AC60xFUE +MCU_NXP_S08:MC9S08AC60xPUE +MCU_NXP_S08:MC9S08AC8xFDE +MCU_NXP_S08:MC9S08AC8xFGE +MCU_NXP_S08:MC9S08AC8xFJE +MCU_NXP_S08:MC9S08AC96xFDE +MCU_NXP_S08:MC9S08AC96xFGE +MCU_NXP_S08:MC9S08AC96xFUE +MCU_NXP_S08:MC9S08AC96xLKE +MCU_NXP_S08:MC9S08AC96xPUE +MCU_NXP_S08:MC9S08AW16AE0xLC +MCU_NXP_S08:MC9S08AW16xFDE +MCU_NXP_S08:MC9S08AW16xFGE +MCU_NXP_S08:MC9S08AW16xFUE +MCU_NXP_S08:MC9S08AW16xPUE +MCU_NXP_S08:MC9S08AW32xFDE +MCU_NXP_S08:MC9S08AW32xFGE +MCU_NXP_S08:MC9S08AW32xFUE +MCU_NXP_S08:MC9S08AW32xPUE +MCU_NXP_S08:MC9S08AW48xFDE +MCU_NXP_S08:MC9S08AW48xFGE +MCU_NXP_S08:MC9S08AW48xFUE +MCU_NXP_S08:MC9S08AW48xPUE +MCU_NXP_S08:MC9S08AW60xFDE +MCU_NXP_S08:MC9S08AW60xFGE +MCU_NXP_S08:MC9S08AW60xFUE +MCU_NXP_S08:MC9S08AW60xPUE +MCU_NXP_S08:MC9S08AW8AE0xLC +MCU_NXP_S08:MC9S08DN16xLC +MCU_NXP_S08:MC9S08DN16xLF +MCU_NXP_S08:MC9S08DN32xLC +MCU_NXP_S08:MC9S08DN32xLF +MCU_NXP_S08:MC9S08DN32xLH +MCU_NXP_S08:MC9S08DN48xLC +MCU_NXP_S08:MC9S08DN48xLF +MCU_NXP_S08:MC9S08DN48xLH +MCU_NXP_S08:MC9S08DN60xLC +MCU_NXP_S08:MC9S08DN60xLF +MCU_NXP_S08:MC9S08DN60xLH +MCU_NXP_S08:MC9S08DV16xLC +MCU_NXP_S08:MC9S08DV16xLF +MCU_NXP_S08:MC9S08DV32xLC +MCU_NXP_S08:MC9S08DV32xLF +MCU_NXP_S08:MC9S08DV32xLH +MCU_NXP_S08:MC9S08DV48xLC +MCU_NXP_S08:MC9S08DV48xLF +MCU_NXP_S08:MC9S08DV48xLH +MCU_NXP_S08:MC9S08DV60xLC +MCU_NXP_S08:MC9S08DV60xLF +MCU_NXP_S08:MC9S08DV60xLH +MCU_NXP_S08:MC9S08DZ128xLF +MCU_NXP_S08:MC9S08DZ128xLH +MCU_NXP_S08:MC9S08DZ128xLL +MCU_NXP_S08:MC9S08DZ16xLC +MCU_NXP_S08:MC9S08DZ16xLF +MCU_NXP_S08:MC9S08DZ32xLC +MCU_NXP_S08:MC9S08DZ32xLF +MCU_NXP_S08:MC9S08DZ32xLH +MCU_NXP_S08:MC9S08DZ48xLC +MCU_NXP_S08:MC9S08DZ48xLF +MCU_NXP_S08:MC9S08DZ48xLH +MCU_NXP_S08:MC9S08DZ60xLC +MCU_NXP_S08:MC9S08DZ60xLF +MCU_NXP_S08:MC9S08DZ60xLH +MCU_NXP_S08:MC9S08DZ96xLF +MCU_NXP_S08:MC9S08DZ96xLH +MCU_NXP_S08:MC9S08DZ96xLL +MCU_NXP_S08:MC9S08EL16xTJ +MCU_NXP_S08:MC9S08EL16xTL +MCU_NXP_S08:MC9S08EL32xTJ +MCU_NXP_S08:MC9S08EL32xTL +MCU_NXP_S08:MC9S08FL16xLC +MCU_NXP_S08:MC9S08FL8xLC +MCU_NXP_S08:MC9S08JM16xGT +MCU_NXP_S08:MC9S08JM16xLC +MCU_NXP_S08:MC9S08JM16xLD +MCU_NXP_S08:MC9S08JM32xGT +MCU_NXP_S08:MC9S08JM32xLD +MCU_NXP_S08:MC9S08JM32xLH +MCU_NXP_S08:MC9S08JM60xGT +MCU_NXP_S08:MC9S08JM60xLD +MCU_NXP_S08:MC9S08JM60xLH +MCU_NXP_S08:MC9S08JM8xGT +MCU_NXP_S08:MC9S08JM8xLC +MCU_NXP_S08:MC9S08JM8xLD +MCU_NXP_S08:MC9S08JS16CFK +MCU_NXP_S08:MC9S08JS16CWJ +MCU_NXP_S08:MC9S08JS8CFK +MCU_NXP_S08:MC9S08JS8CWJ +MCU_NXP_S08:MC9S08LG32J0xLF +MCU_NXP_S08:MC9S08LG32J0xLH +MCU_NXP_S08:MC9S08LG32J0xLK +MCU_NXP_S08:MC9S08MP16xLC +MCU_NXP_S08:MC9S08MP16xLF +MCU_NXP_S08:MC9S08MP16xWL +MCU_NXP_S08:MC9S08QA2CDNE +MCU_NXP_S08:MC9S08QA2CFQE +MCU_NXP_S08:MC9S08QA2CPAE +MCU_NXP_S08:MC9S08QA4CDNE +MCU_NXP_S08:MC9S08QA4CFQE +MCU_NXP_S08:MC9S08QA4CPAE +MCU_NXP_S08:MC9S08QB4xGK +MCU_NXP_S08:MC9S08QB4xTG +MCU_NXP_S08:MC9S08QB4xWL +MCU_NXP_S08:MC9S08QB8xGK +MCU_NXP_S08:MC9S08QB8xTG +MCU_NXP_S08:MC9S08QB8xWL +MCU_NXP_S08:MC9S08QD2xPC +MCU_NXP_S08:MC9S08QD2xSC +MCU_NXP_S08:MC9S08QD4xPC +MCU_NXP_S08:MC9S08QD4xSC +MCU_NXP_S08:MC9S08QG4xDNE +MCU_NXP_S08:MC9S08QG4xDTE +MCU_NXP_S08:MC9S08QG4xFKE +MCU_NXP_S08:MC9S08QG4xFQE +MCU_NXP_S08:MC9S08QG4xPAE +MCU_NXP_S08:MC9S08QG8xDNE +MCU_NXP_S08:MC9S08QG8xDTE +MCU_NXP_S08:MC9S08QG8xFKE +MCU_NXP_S08:MC9S08QG8xFQE +MCU_NXP_S08:MC9S08QG8xPBE +MCU_NXP_S08:MC9S08SC4xTG +MCU_NXP_S08:MC9S08SE4xRL +MCU_NXP_S08:MC9S08SE4xTG +MCU_NXP_S08:MC9S08SE4xWL +MCU_NXP_S08:MC9S08SE8xRL +MCU_NXP_S08:MC9S08SE8xTG +MCU_NXP_S08:MC9S08SE8xWL +MCU_NXP_S08:MC9S08SF4xTG +MCU_NXP_S08:MC9S08SF4xTJ +MCU_NXP_S08:MC9S08SG16xTG +MCU_NXP_S08:MC9S08SG16xTJ +MCU_NXP_S08:MC9S08SG16xTL +MCU_NXP_S08:MC9S08SG32xTG +MCU_NXP_S08:MC9S08SG32xTJ +MCU_NXP_S08:MC9S08SG32xTL +MCU_NXP_S08:MC9S08SG4xSC +MCU_NXP_S08:MC9S08SG4xTG +MCU_NXP_S08:MC9S08SG4xTJ +MCU_NXP_S08:MC9S08SG8xSC +MCU_NXP_S08:MC9S08SG8xTG +MCU_NXP_S08:MC9S08SG8xTJ +MCU_NXP_S08:MC9S08SH16xTG +MCU_NXP_S08:MC9S08SH16xTJ +MCU_NXP_S08:MC9S08SH16xTL +MCU_NXP_S08:MC9S08SH16xWL +MCU_NXP_S08:MC9S08SH32xTG +MCU_NXP_S08:MC9S08SH32xTJ +MCU_NXP_S08:MC9S08SH32xTL +MCU_NXP_S08:MC9S08SH32xWL +MCU_NXP_S08:MC9S08SH4xFK +MCU_NXP_S08:MC9S08SH4xPJ +MCU_NXP_S08:MC9S08SH4xSC +MCU_NXP_S08:MC9S08SH4xTG +MCU_NXP_S08:MC9S08SH4xTJ +MCU_NXP_S08:MC9S08SH4xWJ +MCU_NXP_S08:MC9S08SH8xFK +MCU_NXP_S08:MC9S08SH8xPJ +MCU_NXP_S08:MC9S08SH8xSC +MCU_NXP_S08:MC9S08SH8xTG +MCU_NXP_S08:MC9S08SH8xTJ +MCU_NXP_S08:MC9S08SH8xWJ +MCU_NXP_S08:MC9S08SL16xTJ +MCU_NXP_S08:MC9S08SL16xTL +MCU_NXP_S08:MC9S08SL32xTJ +MCU_NXP_S08:MC9S08SL32xTL +MCU_NXP_S08:MC9S08SV16CLC +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 +MCU_SiliconLabs:C8051F382-GQ +MCU_SiliconLabs:C8051F383-GM +MCU_SiliconLabs:C8051F383-GQ +MCU_SiliconLabs:C8051F384-GQ +MCU_SiliconLabs:C8051F385-GM +MCU_SiliconLabs:C8051F385-GQ +MCU_SiliconLabs:C8051F386-GQ +MCU_SiliconLabs:C8051F387-GM +MCU_SiliconLabs:C8051F387-GQ +MCU_SiliconLabs:C8051F38C-GM +MCU_SiliconLabs:C8051F38C-GQ +MCU_SiliconLabs:EFM32G230F128G-E-QFN64 +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 +MCU_SiliconLabs:EFM8BB10F4A-A-QFN20 +MCU_SiliconLabs:EFM8BB10F4G-A-QFN20 +MCU_SiliconLabs:EFM8BB10F4I-A-QFN20 +MCU_SiliconLabs:EFM8BB10F8A-A-QFN20 +MCU_SiliconLabs:EFM8BB10F8G-A-QFN20 +MCU_SiliconLabs:EFM8BB10F8G-A-QSOP24 +MCU_SiliconLabs:EFM8BB10F8G-A-SOIC16 +MCU_SiliconLabs:EFM8BB10F8I-A-QFN20 +MCU_SiliconLabs:EFM8BB10F8I-A-QSOP24 +MCU_SiliconLabs:EFM8BB10F8I-A-SOIC16 +MCU_SiliconLabs:EFM8LB12F32E-C-QFP32 +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 +MCU_ST_STM32C0:STM32C011F6Px +MCU_ST_STM32C0:STM32C011F6Ux +MCU_ST_STM32C0:STM32C011F_4-6_Px +MCU_ST_STM32C0:STM32C011F_4-6_Ux +MCU_ST_STM32C0:STM32C011J4Mx +MCU_ST_STM32C0:STM32C011J6Mx +MCU_ST_STM32C0:STM32C011J_4-6_Mx +MCU_ST_STM32C0:STM32C031C4Tx +MCU_ST_STM32C0:STM32C031C4Ux +MCU_ST_STM32C0:STM32C031C6Tx +MCU_ST_STM32C0:STM32C031C6Ux +MCU_ST_STM32C0:STM32C031C_4-6_Tx +MCU_ST_STM32C0:STM32C031C_4-6_Ux +MCU_ST_STM32C0:STM32C031F4Px +MCU_ST_STM32C0:STM32C031F6Px +MCU_ST_STM32C0:STM32C031F_4-6_Px +MCU_ST_STM32C0:STM32C031G4Ux +MCU_ST_STM32C0:STM32C031G6Ux +MCU_ST_STM32C0:STM32C031G_4-6_Ux +MCU_ST_STM32C0:STM32C031K4Tx +MCU_ST_STM32C0:STM32C031K4Ux +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 +MCU_ST_STM32F0:STM32F030F4Px +MCU_ST_STM32F0:STM32F030K6Tx +MCU_ST_STM32F0:STM32F030R8Tx +MCU_ST_STM32F0:STM32F030RCTx +MCU_ST_STM32F0:STM32F031C4Tx +MCU_ST_STM32F0:STM32F031C6Tx +MCU_ST_STM32F0:STM32F031C_4-6_Tx +MCU_ST_STM32F0:STM32F031E6Yx +MCU_ST_STM32F0:STM32F031F4Px +MCU_ST_STM32F0:STM32F031F6Px +MCU_ST_STM32F0:STM32F031F_4-6_Px +MCU_ST_STM32F0:STM32F031G4Ux +MCU_ST_STM32F0:STM32F031G6Ux +MCU_ST_STM32F0:STM32F031G_4-6_Ux +MCU_ST_STM32F0:STM32F031K4Ux +MCU_ST_STM32F0:STM32F031K6Tx +MCU_ST_STM32F0:STM32F031K6Ux +MCU_ST_STM32F0:STM32F031K_4-6_Ux +MCU_ST_STM32F0:STM32F038C6Tx +MCU_ST_STM32F0:STM32F038E6Yx +MCU_ST_STM32F0:STM32F038F6Px +MCU_ST_STM32F0:STM32F038G6Ux +MCU_ST_STM32F0:STM32F038K6Ux +MCU_ST_STM32F0:STM32F042C4Tx +MCU_ST_STM32F0:STM32F042C4Ux +MCU_ST_STM32F0:STM32F042C6Tx +MCU_ST_STM32F0:STM32F042C6Ux +MCU_ST_STM32F0:STM32F042C_4-6_Tx +MCU_ST_STM32F0:STM32F042C_4-6_Ux +MCU_ST_STM32F0:STM32F042F4Px +MCU_ST_STM32F0:STM32F042F6Px +MCU_ST_STM32F0:STM32F042G4Ux +MCU_ST_STM32F0:STM32F042G6Ux +MCU_ST_STM32F0:STM32F042G_4-6_Ux +MCU_ST_STM32F0:STM32F042K4Tx +MCU_ST_STM32F0:STM32F042K4Ux +MCU_ST_STM32F0:STM32F042K6Tx +MCU_ST_STM32F0:STM32F042K6Ux +MCU_ST_STM32F0:STM32F042K_4-6_Tx +MCU_ST_STM32F0:STM32F042K_4-6_Ux +MCU_ST_STM32F0:STM32F042T6Yx +MCU_ST_STM32F0:STM32F048C6Ux +MCU_ST_STM32F0:STM32F048G6Ux +MCU_ST_STM32F0:STM32F048T6Yx +MCU_ST_STM32F0:STM32F051C4Tx +MCU_ST_STM32F0:STM32F051C4Ux +MCU_ST_STM32F0:STM32F051C6Tx +MCU_ST_STM32F0:STM32F051C6Ux +MCU_ST_STM32F0:STM32F051C8Tx +MCU_ST_STM32F0:STM32F051C8Ux +MCU_ST_STM32F0:STM32F051K4Tx +MCU_ST_STM32F0:STM32F051K4Ux +MCU_ST_STM32F0:STM32F051K6Tx +MCU_ST_STM32F0:STM32F051K6Ux +MCU_ST_STM32F0:STM32F051K8Tx +MCU_ST_STM32F0:STM32F051K8Ux +MCU_ST_STM32F0:STM32F051R4Tx +MCU_ST_STM32F0:STM32F051R6Tx +MCU_ST_STM32F0:STM32F051R8Hx +MCU_ST_STM32F0:STM32F051R8Tx +MCU_ST_STM32F0:STM32F051T8Yx +MCU_ST_STM32F0:STM32F058C8Ux +MCU_ST_STM32F0:STM32F058R8Hx +MCU_ST_STM32F0:STM32F058R8Tx +MCU_ST_STM32F0:STM32F058T8Yx +MCU_ST_STM32F0:STM32F070C6Tx +MCU_ST_STM32F0:STM32F070CBTx +MCU_ST_STM32F0:STM32F070F6Px +MCU_ST_STM32F0:STM32F070RBTx +MCU_ST_STM32F0:STM32F071C8Tx +MCU_ST_STM32F0:STM32F071C8Ux +MCU_ST_STM32F0:STM32F071CBTx +MCU_ST_STM32F0:STM32F071CBUx +MCU_ST_STM32F0:STM32F071CBYx +MCU_ST_STM32F0:STM32F071C_8-B_Tx +MCU_ST_STM32F0:STM32F071C_8-B_Ux +MCU_ST_STM32F0:STM32F071RBTx +MCU_ST_STM32F0:STM32F071V8Hx +MCU_ST_STM32F0:STM32F071V8Tx +MCU_ST_STM32F0:STM32F071VBHx +MCU_ST_STM32F0:STM32F071VBTx +MCU_ST_STM32F0:STM32F071V_8-B_Hx +MCU_ST_STM32F0:STM32F071V_8-B_Tx +MCU_ST_STM32F0:STM32F072C8Tx +MCU_ST_STM32F0:STM32F072C8Ux +MCU_ST_STM32F0:STM32F072CBTx +MCU_ST_STM32F0:STM32F072CBUx +MCU_ST_STM32F0:STM32F072CBYx +MCU_ST_STM32F0:STM32F072C_8-B_Tx +MCU_ST_STM32F0:STM32F072C_8-B_Ux +MCU_ST_STM32F0:STM32F072R8Tx +MCU_ST_STM32F0:STM32F072RBHx +MCU_ST_STM32F0:STM32F072RBIx +MCU_ST_STM32F0:STM32F072RBTx +MCU_ST_STM32F0:STM32F072R_8-B_Tx +MCU_ST_STM32F0:STM32F072V8Hx +MCU_ST_STM32F0:STM32F072V8Tx +MCU_ST_STM32F0:STM32F072VBHx +MCU_ST_STM32F0:STM32F072VBTx +MCU_ST_STM32F0:STM32F072V_8-B_Hx +MCU_ST_STM32F0:STM32F072V_8-B_Tx +MCU_ST_STM32F0:STM32F078CBTx +MCU_ST_STM32F0:STM32F078CBUx +MCU_ST_STM32F0:STM32F078CBYx +MCU_ST_STM32F0:STM32F078RBHx +MCU_ST_STM32F0:STM32F078RBTx +MCU_ST_STM32F0:STM32F078VBHx +MCU_ST_STM32F0:STM32F078VBTx +MCU_ST_STM32F0:STM32F091CBTx +MCU_ST_STM32F0:STM32F091CBUx +MCU_ST_STM32F0:STM32F091CCTx +MCU_ST_STM32F0:STM32F091CCUx +MCU_ST_STM32F0:STM32F091C_B-C_Tx +MCU_ST_STM32F0:STM32F091C_B-C_Ux +MCU_ST_STM32F0:STM32F091RBTx +MCU_ST_STM32F0:STM32F091RCHx +MCU_ST_STM32F0:STM32F091RCTx +MCU_ST_STM32F0:STM32F091RCYx +MCU_ST_STM32F0:STM32F091R_B-C_Tx +MCU_ST_STM32F0:STM32F091VBTx +MCU_ST_STM32F0:STM32F091VCHx +MCU_ST_STM32F0:STM32F091VCTx +MCU_ST_STM32F0:STM32F091V_B-C_Tx +MCU_ST_STM32F0:STM32F098CCTx +MCU_ST_STM32F0:STM32F098CCUx +MCU_ST_STM32F0:STM32F098RCHx +MCU_ST_STM32F0:STM32F098RCTx +MCU_ST_STM32F0:STM32F098RCYx +MCU_ST_STM32F0:STM32F098VCHx +MCU_ST_STM32F0:STM32F098VCTx +MCU_ST_STM32F1:STM32F100C4Tx +MCU_ST_STM32F1:STM32F100C6Tx +MCU_ST_STM32F1:STM32F100C8Tx +MCU_ST_STM32F1:STM32F100CBTx +MCU_ST_STM32F1:STM32F100C_4-6_Tx +MCU_ST_STM32F1:STM32F100C_8-B_Tx +MCU_ST_STM32F1:STM32F100R4Hx +MCU_ST_STM32F1:STM32F100R4Tx +MCU_ST_STM32F1:STM32F100R6Hx +MCU_ST_STM32F1:STM32F100R6Tx +MCU_ST_STM32F1:STM32F100R8Hx +MCU_ST_STM32F1:STM32F100R8Tx +MCU_ST_STM32F1:STM32F100RBHx +MCU_ST_STM32F1:STM32F100RBTx +MCU_ST_STM32F1:STM32F100RCTx +MCU_ST_STM32F1:STM32F100RDTx +MCU_ST_STM32F1:STM32F100RETx +MCU_ST_STM32F1:STM32F100R_4-6_Hx +MCU_ST_STM32F1:STM32F100R_4-6_Tx +MCU_ST_STM32F1:STM32F100R_8-B_Hx +MCU_ST_STM32F1:STM32F100R_8-B_Tx +MCU_ST_STM32F1:STM32F100R_C-D-E_Tx +MCU_ST_STM32F1:STM32F100V8Tx +MCU_ST_STM32F1:STM32F100VBTx +MCU_ST_STM32F1:STM32F100VCTx +MCU_ST_STM32F1:STM32F100VDTx +MCU_ST_STM32F1:STM32F100VETx +MCU_ST_STM32F1:STM32F100V_8-B_Tx +MCU_ST_STM32F1:STM32F100V_C-D-E_Tx +MCU_ST_STM32F1:STM32F100ZCTx +MCU_ST_STM32F1:STM32F100ZDTx +MCU_ST_STM32F1:STM32F100ZETx +MCU_ST_STM32F1:STM32F100Z_C-D-E_Tx +MCU_ST_STM32F1:STM32F101C4Tx +MCU_ST_STM32F1:STM32F101C6Tx +MCU_ST_STM32F1:STM32F101C8Tx +MCU_ST_STM32F1:STM32F101C8Ux +MCU_ST_STM32F1:STM32F101CBTx +MCU_ST_STM32F1:STM32F101CBUx +MCU_ST_STM32F1:STM32F101C_4-6_Tx +MCU_ST_STM32F1:STM32F101C_8-B_Tx +MCU_ST_STM32F1:STM32F101C_8-B_Ux +MCU_ST_STM32F1:STM32F101R4Tx +MCU_ST_STM32F1:STM32F101R6Tx +MCU_ST_STM32F1:STM32F101R8Tx +MCU_ST_STM32F1:STM32F101RBHx +MCU_ST_STM32F1:STM32F101RBTx +MCU_ST_STM32F1:STM32F101RCTx +MCU_ST_STM32F1:STM32F101RDTx +MCU_ST_STM32F1:STM32F101RETx +MCU_ST_STM32F1:STM32F101RFTx +MCU_ST_STM32F1:STM32F101RGTx +MCU_ST_STM32F1:STM32F101R_4-6_Tx +MCU_ST_STM32F1:STM32F101R_8-B_Tx +MCU_ST_STM32F1:STM32F101R_C-D-E_Tx +MCU_ST_STM32F1:STM32F101R_F-G_Tx +MCU_ST_STM32F1:STM32F101T4Ux +MCU_ST_STM32F1:STM32F101T6Ux +MCU_ST_STM32F1:STM32F101T8Ux +MCU_ST_STM32F1:STM32F101TBUx +MCU_ST_STM32F1:STM32F101T_4-6_Ux +MCU_ST_STM32F1:STM32F101T_8-B_Ux +MCU_ST_STM32F1:STM32F101V8Tx +MCU_ST_STM32F1:STM32F101VBTx +MCU_ST_STM32F1:STM32F101VCTx +MCU_ST_STM32F1:STM32F101VDTx +MCU_ST_STM32F1:STM32F101VETx +MCU_ST_STM32F1:STM32F101VFTx +MCU_ST_STM32F1:STM32F101VGTx +MCU_ST_STM32F1:STM32F101V_8-B_Tx +MCU_ST_STM32F1:STM32F101V_C-D-E_Tx +MCU_ST_STM32F1:STM32F101V_F-G_Tx +MCU_ST_STM32F1:STM32F101ZCTx +MCU_ST_STM32F1:STM32F101ZDTx +MCU_ST_STM32F1:STM32F101ZETx +MCU_ST_STM32F1:STM32F101ZFTx +MCU_ST_STM32F1:STM32F101ZGTx +MCU_ST_STM32F1:STM32F101Z_C-D-E_Tx +MCU_ST_STM32F1:STM32F101Z_F-G_Tx +MCU_ST_STM32F1:STM32F102C4Tx +MCU_ST_STM32F1:STM32F102C6Tx +MCU_ST_STM32F1:STM32F102C8Tx +MCU_ST_STM32F1:STM32F102CBTx +MCU_ST_STM32F1:STM32F102C_4-6_Tx +MCU_ST_STM32F1:STM32F102C_8-B_Tx +MCU_ST_STM32F1:STM32F102R4Tx +MCU_ST_STM32F1:STM32F102R6Tx +MCU_ST_STM32F1:STM32F102R8Tx +MCU_ST_STM32F1:STM32F102RBTx +MCU_ST_STM32F1:STM32F102R_4-6_Tx +MCU_ST_STM32F1:STM32F102R_8-B_Tx +MCU_ST_STM32F1:STM32F103C4Tx +MCU_ST_STM32F1:STM32F103C6Tx +MCU_ST_STM32F1:STM32F103C6Ux +MCU_ST_STM32F1:STM32F103C8Tx +MCU_ST_STM32F1:STM32F103CBTx +MCU_ST_STM32F1:STM32F103CBUx +MCU_ST_STM32F1:STM32F103C_4-6_Tx +MCU_ST_STM32F1:STM32F103C_8-B_Tx +MCU_ST_STM32F1:STM32F103R4Hx +MCU_ST_STM32F1:STM32F103R4Tx +MCU_ST_STM32F1:STM32F103R6Hx +MCU_ST_STM32F1:STM32F103R6Tx +MCU_ST_STM32F1:STM32F103R8Hx +MCU_ST_STM32F1:STM32F103R8Tx +MCU_ST_STM32F1:STM32F103RBHx +MCU_ST_STM32F1:STM32F103RBTx +MCU_ST_STM32F1:STM32F103RCTx +MCU_ST_STM32F1:STM32F103RCYx +MCU_ST_STM32F1:STM32F103RDTx +MCU_ST_STM32F1:STM32F103RDYx +MCU_ST_STM32F1:STM32F103RETx +MCU_ST_STM32F1:STM32F103REYx +MCU_ST_STM32F1:STM32F103RFTx +MCU_ST_STM32F1:STM32F103RGTx +MCU_ST_STM32F1:STM32F103R_4-6_Hx +MCU_ST_STM32F1:STM32F103R_4-6_Tx +MCU_ST_STM32F1:STM32F103R_8-B_Hx +MCU_ST_STM32F1:STM32F103R_8-B_Tx +MCU_ST_STM32F1:STM32F103R_C-D-E_Tx +MCU_ST_STM32F1:STM32F103R_C-D-E_Yx +MCU_ST_STM32F1:STM32F103R_F-G_Tx +MCU_ST_STM32F1:STM32F103T4Ux +MCU_ST_STM32F1:STM32F103T6Ux +MCU_ST_STM32F1:STM32F103T8Ux +MCU_ST_STM32F1:STM32F103TBUx +MCU_ST_STM32F1:STM32F103T_4-6_Ux +MCU_ST_STM32F1:STM32F103T_8-B_Ux +MCU_ST_STM32F1:STM32F103V8Hx +MCU_ST_STM32F1:STM32F103V8Tx +MCU_ST_STM32F1:STM32F103VBHx +MCU_ST_STM32F1:STM32F103VBIx +MCU_ST_STM32F1:STM32F103VBTx +MCU_ST_STM32F1:STM32F103VCHx +MCU_ST_STM32F1:STM32F103VCTx +MCU_ST_STM32F1:STM32F103VDHx +MCU_ST_STM32F1:STM32F103VDTx +MCU_ST_STM32F1:STM32F103VEHx +MCU_ST_STM32F1:STM32F103VETx +MCU_ST_STM32F1:STM32F103VFTx +MCU_ST_STM32F1:STM32F103VGTx +MCU_ST_STM32F1:STM32F103V_8-B_Hx +MCU_ST_STM32F1:STM32F103V_8-B_Tx +MCU_ST_STM32F1:STM32F103V_C-D-E_Hx +MCU_ST_STM32F1:STM32F103V_C-D-E_Tx +MCU_ST_STM32F1:STM32F103V_F-G_Tx +MCU_ST_STM32F1:STM32F103ZCHx +MCU_ST_STM32F1:STM32F103ZCTx +MCU_ST_STM32F1:STM32F103ZDHx +MCU_ST_STM32F1:STM32F103ZDTx +MCU_ST_STM32F1:STM32F103ZEHx +MCU_ST_STM32F1:STM32F103ZETx +MCU_ST_STM32F1:STM32F103ZFHx +MCU_ST_STM32F1:STM32F103ZFTx +MCU_ST_STM32F1:STM32F103ZGHx +MCU_ST_STM32F1:STM32F103ZGTx +MCU_ST_STM32F1:STM32F103Z_C-D-E_Hx +MCU_ST_STM32F1:STM32F103Z_C-D-E_Tx +MCU_ST_STM32F1:STM32F103Z_F-G_Hx +MCU_ST_STM32F1:STM32F103Z_F-G_Tx +MCU_ST_STM32F1:STM32F105R8Tx +MCU_ST_STM32F1:STM32F105RBTx +MCU_ST_STM32F1:STM32F105RCTx +MCU_ST_STM32F1:STM32F105R_8-B-C_Tx +MCU_ST_STM32F1:STM32F105V8Hx +MCU_ST_STM32F1:STM32F105V8Tx +MCU_ST_STM32F1:STM32F105VBHx +MCU_ST_STM32F1:STM32F105VBTx +MCU_ST_STM32F1:STM32F105VCTx +MCU_ST_STM32F1:STM32F105V_8-B-C_Tx +MCU_ST_STM32F1:STM32F105V_8-B_Hx +MCU_ST_STM32F1:STM32F107RBTx +MCU_ST_STM32F1:STM32F107RCTx +MCU_ST_STM32F1:STM32F107R_B-C_Tx +MCU_ST_STM32F1:STM32F107VBTx +MCU_ST_STM32F1:STM32F107VCHx +MCU_ST_STM32F1:STM32F107VCTx +MCU_ST_STM32F1:STM32F107V_B-C_Tx +MCU_ST_STM32F2:STM32F205RBTx +MCU_ST_STM32F2:STM32F205RCTx +MCU_ST_STM32F2:STM32F205RETx +MCU_ST_STM32F2:STM32F205REYx +MCU_ST_STM32F2:STM32F205RFTx +MCU_ST_STM32F2:STM32F205RGEx +MCU_ST_STM32F2:STM32F205RGTx +MCU_ST_STM32F2:STM32F205RGYx +MCU_ST_STM32F2:STM32F205R_B-C-E-F-G_Tx +MCU_ST_STM32F2:STM32F205R_E-G_Yx +MCU_ST_STM32F2:STM32F205VBTx +MCU_ST_STM32F2:STM32F205VCTx +MCU_ST_STM32F2:STM32F205VETx +MCU_ST_STM32F2:STM32F205VFTx +MCU_ST_STM32F2:STM32F205VGTx +MCU_ST_STM32F2:STM32F205V_B-C-E-F-G_Tx +MCU_ST_STM32F2:STM32F205ZCTx +MCU_ST_STM32F2:STM32F205ZETx +MCU_ST_STM32F2:STM32F205ZFTx +MCU_ST_STM32F2:STM32F205ZGTx +MCU_ST_STM32F2:STM32F205Z_C-E-F-G_Tx +MCU_ST_STM32F2:STM32F207ICHx +MCU_ST_STM32F2:STM32F207ICTx +MCU_ST_STM32F2:STM32F207IEHx +MCU_ST_STM32F2:STM32F207IETx +MCU_ST_STM32F2:STM32F207IFHx +MCU_ST_STM32F2:STM32F207IFTx +MCU_ST_STM32F2:STM32F207IGHx +MCU_ST_STM32F2:STM32F207IGTx +MCU_ST_STM32F2:STM32F207I_C-E-F-G_Hx +MCU_ST_STM32F2:STM32F207I_C-E-F-G_Tx +MCU_ST_STM32F2:STM32F207VCTx +MCU_ST_STM32F2:STM32F207VETx +MCU_ST_STM32F2:STM32F207VFTx +MCU_ST_STM32F2:STM32F207VGTx +MCU_ST_STM32F2:STM32F207V_C-E-F-G_Tx +MCU_ST_STM32F2:STM32F207ZCTx +MCU_ST_STM32F2:STM32F207ZETx +MCU_ST_STM32F2:STM32F207ZFTx +MCU_ST_STM32F2:STM32F207ZGTx +MCU_ST_STM32F2:STM32F207Z_C-E-F-G_Tx +MCU_ST_STM32F2:STM32F215RETx +MCU_ST_STM32F2:STM32F215RGTx +MCU_ST_STM32F2:STM32F215R_E-G_Tx +MCU_ST_STM32F2:STM32F215VETx +MCU_ST_STM32F2:STM32F215VGTx +MCU_ST_STM32F2:STM32F215V_E-G_Tx +MCU_ST_STM32F2:STM32F215ZETx +MCU_ST_STM32F2:STM32F215ZGTx +MCU_ST_STM32F2:STM32F215Z_E-G_Tx +MCU_ST_STM32F2:STM32F217IEHx +MCU_ST_STM32F2:STM32F217IETx +MCU_ST_STM32F2:STM32F217IGHx +MCU_ST_STM32F2:STM32F217IGTx +MCU_ST_STM32F2:STM32F217I_E-G_Hx +MCU_ST_STM32F2:STM32F217I_E-G_Tx +MCU_ST_STM32F2:STM32F217VETx +MCU_ST_STM32F2:STM32F217VGTx +MCU_ST_STM32F2:STM32F217V_E-G_Tx +MCU_ST_STM32F2:STM32F217ZETx +MCU_ST_STM32F2:STM32F217ZGTx +MCU_ST_STM32F2:STM32F217Z_E-G_Tx +MCU_ST_STM32F3:STM32F301C6Tx +MCU_ST_STM32F3:STM32F301C8Tx +MCU_ST_STM32F3:STM32F301C8Yx +MCU_ST_STM32F3:STM32F301C_6-8_Tx +MCU_ST_STM32F3:STM32F301K6Tx +MCU_ST_STM32F3:STM32F301K6Ux +MCU_ST_STM32F3:STM32F301K8Tx +MCU_ST_STM32F3:STM32F301K8Ux +MCU_ST_STM32F3:STM32F301K_6-8_Tx +MCU_ST_STM32F3:STM32F301K_6-8_Ux +MCU_ST_STM32F3:STM32F301R6Tx +MCU_ST_STM32F3:STM32F301R8Tx +MCU_ST_STM32F3:STM32F301R_6-8_Tx +MCU_ST_STM32F3:STM32F302C6Tx +MCU_ST_STM32F3:STM32F302C8Tx +MCU_ST_STM32F3:STM32F302C8Yx +MCU_ST_STM32F3:STM32F302CBTx +MCU_ST_STM32F3:STM32F302CCTx +MCU_ST_STM32F3:STM32F302C_6-8_Tx +MCU_ST_STM32F3:STM32F302C_B-C_Tx +MCU_ST_STM32F3:STM32F302K6Ux +MCU_ST_STM32F3:STM32F302K8Ux +MCU_ST_STM32F3:STM32F302K_6-8_Ux +MCU_ST_STM32F3:STM32F302R6Tx +MCU_ST_STM32F3:STM32F302R8Tx +MCU_ST_STM32F3:STM32F302RBTx +MCU_ST_STM32F3:STM32F302RCTx +MCU_ST_STM32F3:STM32F302RDTx +MCU_ST_STM32F3:STM32F302RETx +MCU_ST_STM32F3:STM32F302R_6-8_Tx +MCU_ST_STM32F3:STM32F302R_B-C_Tx +MCU_ST_STM32F3:STM32F302R_D-E_Tx +MCU_ST_STM32F3:STM32F302VBTx +MCU_ST_STM32F3:STM32F302VCTx +MCU_ST_STM32F3:STM32F302VCYx +MCU_ST_STM32F3:STM32F302VDHx +MCU_ST_STM32F3:STM32F302VDTx +MCU_ST_STM32F3:STM32F302VEHx +MCU_ST_STM32F3:STM32F302VETx +MCU_ST_STM32F3:STM32F302V_B-C_Tx +MCU_ST_STM32F3:STM32F302V_D-E_Hx +MCU_ST_STM32F3:STM32F302V_D-E_Tx +MCU_ST_STM32F3:STM32F302ZDTx +MCU_ST_STM32F3:STM32F302ZETx +MCU_ST_STM32F3:STM32F302Z_D-E_Tx +MCU_ST_STM32F3:STM32F303C6Tx +MCU_ST_STM32F3:STM32F303C8Tx +MCU_ST_STM32F3:STM32F303C8Yx +MCU_ST_STM32F3:STM32F303CBTx +MCU_ST_STM32F3:STM32F303CCTx +MCU_ST_STM32F3:STM32F303C_6-8_Tx +MCU_ST_STM32F3:STM32F303C_B-C_Tx +MCU_ST_STM32F3:STM32F303K6Tx +MCU_ST_STM32F3:STM32F303K6Ux +MCU_ST_STM32F3:STM32F303K8Tx +MCU_ST_STM32F3:STM32F303K8Ux +MCU_ST_STM32F3:STM32F303K_6-8_Tx +MCU_ST_STM32F3:STM32F303K_6-8_Ux +MCU_ST_STM32F3:STM32F303R6Tx +MCU_ST_STM32F3:STM32F303R8Tx +MCU_ST_STM32F3:STM32F303RBTx +MCU_ST_STM32F3:STM32F303RCTx +MCU_ST_STM32F3:STM32F303RDTx +MCU_ST_STM32F3:STM32F303RETx +MCU_ST_STM32F3:STM32F303R_6-8_Tx +MCU_ST_STM32F3:STM32F303R_B-C_Tx +MCU_ST_STM32F3:STM32F303R_D-E_Tx +MCU_ST_STM32F3:STM32F303VBTx +MCU_ST_STM32F3:STM32F303VCTx +MCU_ST_STM32F3:STM32F303VCYx +MCU_ST_STM32F3:STM32F303VDHx +MCU_ST_STM32F3:STM32F303VDTx +MCU_ST_STM32F3:STM32F303VEHx +MCU_ST_STM32F3:STM32F303VETx +MCU_ST_STM32F3:STM32F303VEYx +MCU_ST_STM32F3:STM32F303V_B-C_Tx +MCU_ST_STM32F3:STM32F303V_D-E_Hx +MCU_ST_STM32F3:STM32F303V_D-E_Tx +MCU_ST_STM32F3:STM32F303ZDTx +MCU_ST_STM32F3:STM32F303ZETx +MCU_ST_STM32F3:STM32F303Z_D-E_Tx +MCU_ST_STM32F3:STM32F318C8Tx +MCU_ST_STM32F3:STM32F318C8Yx +MCU_ST_STM32F3:STM32F318K8Ux +MCU_ST_STM32F3:STM32F328C8Tx +MCU_ST_STM32F3:STM32F334C4Tx +MCU_ST_STM32F3:STM32F334C6Tx +MCU_ST_STM32F3:STM32F334C8Tx +MCU_ST_STM32F3:STM32F334C8Yx +MCU_ST_STM32F3:STM32F334C_4-6-8_Tx +MCU_ST_STM32F3:STM32F334K4Tx +MCU_ST_STM32F3:STM32F334K4Ux +MCU_ST_STM32F3:STM32F334K6Tx +MCU_ST_STM32F3:STM32F334K6Ux +MCU_ST_STM32F3:STM32F334K8Tx +MCU_ST_STM32F3:STM32F334K8Ux +MCU_ST_STM32F3:STM32F334K_4-6-8_Tx +MCU_ST_STM32F3:STM32F334K_4-6-8_Ux +MCU_ST_STM32F3:STM32F334R6Tx +MCU_ST_STM32F3:STM32F334R8Tx +MCU_ST_STM32F3:STM32F334R_6-8_Tx +MCU_ST_STM32F3:STM32F358CCTx +MCU_ST_STM32F3:STM32F358RCTx +MCU_ST_STM32F3:STM32F358VCTx +MCU_ST_STM32F3:STM32F373C8Tx +MCU_ST_STM32F3:STM32F373CBTx +MCU_ST_STM32F3:STM32F373CCTx +MCU_ST_STM32F3:STM32F373C_8-B-C_Tx +MCU_ST_STM32F3:STM32F373R8Tx +MCU_ST_STM32F3:STM32F373RBTx +MCU_ST_STM32F3:STM32F373RCTx +MCU_ST_STM32F3:STM32F373R_8-B-C_Tx +MCU_ST_STM32F3:STM32F373V8Hx +MCU_ST_STM32F3:STM32F373V8Tx +MCU_ST_STM32F3:STM32F373VBHx +MCU_ST_STM32F3:STM32F373VBTx +MCU_ST_STM32F3:STM32F373VCHx +MCU_ST_STM32F3:STM32F373VCTx +MCU_ST_STM32F3:STM32F373V_8-B-C_Hx +MCU_ST_STM32F3:STM32F373V_8-B-C_Tx +MCU_ST_STM32F3:STM32F378CCTx +MCU_ST_STM32F3:STM32F378RCTx +MCU_ST_STM32F3:STM32F378RCYx +MCU_ST_STM32F3:STM32F378VCHx +MCU_ST_STM32F3:STM32F378VCTx +MCU_ST_STM32F3:STM32F398VETx +MCU_ST_STM32F4:STM32F401CBUx +MCU_ST_STM32F4:STM32F401CBYx +MCU_ST_STM32F4:STM32F401CCFx +MCU_ST_STM32F4:STM32F401CCUx +MCU_ST_STM32F4:STM32F401CCYx +MCU_ST_STM32F4:STM32F401CDUx +MCU_ST_STM32F4:STM32F401CDYx +MCU_ST_STM32F4:STM32F401CEUx +MCU_ST_STM32F4:STM32F401CEYx +MCU_ST_STM32F4:STM32F401C_B-C_Ux +MCU_ST_STM32F4:STM32F401C_B-C_Yx +MCU_ST_STM32F4:STM32F401C_D-E_Ux +MCU_ST_STM32F4:STM32F401C_D-E_Yx +MCU_ST_STM32F4:STM32F401RBTx +MCU_ST_STM32F4:STM32F401RCTx +MCU_ST_STM32F4:STM32F401RDTx +MCU_ST_STM32F4:STM32F401RETx +MCU_ST_STM32F4:STM32F401R_B-C_Tx +MCU_ST_STM32F4:STM32F401R_D-E_Tx +MCU_ST_STM32F4:STM32F401VBHx +MCU_ST_STM32F4:STM32F401VBTx +MCU_ST_STM32F4:STM32F401VCHx +MCU_ST_STM32F4:STM32F401VCTx +MCU_ST_STM32F4:STM32F401VDHx +MCU_ST_STM32F4:STM32F401VDTx +MCU_ST_STM32F4:STM32F401VEHx +MCU_ST_STM32F4:STM32F401VETx +MCU_ST_STM32F4:STM32F401V_B-C_Hx +MCU_ST_STM32F4:STM32F401V_B-C_Tx +MCU_ST_STM32F4:STM32F401V_D-E_Hx +MCU_ST_STM32F4:STM32F401V_D-E_Tx +MCU_ST_STM32F4:STM32F405OEYx +MCU_ST_STM32F4:STM32F405OGYx +MCU_ST_STM32F4:STM32F405O_E-G_Yx +MCU_ST_STM32F4:STM32F405RGTx +MCU_ST_STM32F4:STM32F405VGTx +MCU_ST_STM32F4:STM32F405ZGTx +MCU_ST_STM32F4:STM32F407IEHx +MCU_ST_STM32F4:STM32F407IETx +MCU_ST_STM32F4:STM32F407IGHx +MCU_ST_STM32F4:STM32F407IGTx +MCU_ST_STM32F4:STM32F407I_E-G_Hx +MCU_ST_STM32F4:STM32F407I_E-G_Tx +MCU_ST_STM32F4:STM32F407VETx +MCU_ST_STM32F4:STM32F407VGTx +MCU_ST_STM32F4:STM32F407V_E-G_Tx +MCU_ST_STM32F4:STM32F407ZETx +MCU_ST_STM32F4:STM32F407ZGTx +MCU_ST_STM32F4:STM32F407Z_E-G_Tx +MCU_ST_STM32F4:STM32F410C8Tx +MCU_ST_STM32F4:STM32F410C8Ux +MCU_ST_STM32F4:STM32F410CBTx +MCU_ST_STM32F4:STM32F410CBUx +MCU_ST_STM32F4:STM32F410C_8-B_Tx +MCU_ST_STM32F4:STM32F410C_8-B_Ux +MCU_ST_STM32F4:STM32F410R8Ix +MCU_ST_STM32F4:STM32F410R8Tx +MCU_ST_STM32F4:STM32F410RBIx +MCU_ST_STM32F4:STM32F410RBTx +MCU_ST_STM32F4:STM32F410R_8-B_Ix +MCU_ST_STM32F4:STM32F410R_8-B_Tx +MCU_ST_STM32F4:STM32F410T8Yx +MCU_ST_STM32F4:STM32F410TBYx +MCU_ST_STM32F4:STM32F410T_8-B_Yx +MCU_ST_STM32F4:STM32F411CCUx +MCU_ST_STM32F4:STM32F411CCYx +MCU_ST_STM32F4:STM32F411CEUx +MCU_ST_STM32F4:STM32F411CEYx +MCU_ST_STM32F4:STM32F411C_C-E_Ux +MCU_ST_STM32F4:STM32F411C_C-E_Yx +MCU_ST_STM32F4:STM32F411RCTx +MCU_ST_STM32F4:STM32F411RETx +MCU_ST_STM32F4:STM32F411R_C-E_Tx +MCU_ST_STM32F4:STM32F411VCHx +MCU_ST_STM32F4:STM32F411VCTx +MCU_ST_STM32F4:STM32F411VEHx +MCU_ST_STM32F4:STM32F411VETx +MCU_ST_STM32F4:STM32F411V_C-E_Hx +MCU_ST_STM32F4:STM32F411V_C-E_Tx +MCU_ST_STM32F4:STM32F412CEUx +MCU_ST_STM32F4:STM32F412CGUx +MCU_ST_STM32F4:STM32F412C_E-G_Ux +MCU_ST_STM32F4:STM32F412RETx +MCU_ST_STM32F4:STM32F412REYx +MCU_ST_STM32F4:STM32F412REYxP +MCU_ST_STM32F4:STM32F412RGTx +MCU_ST_STM32F4:STM32F412RGYx +MCU_ST_STM32F4:STM32F412RGYxP +MCU_ST_STM32F4:STM32F412R_E-G_Tx +MCU_ST_STM32F4:STM32F412R_E-G_Yx +MCU_ST_STM32F4:STM32F412R_E-G_YxP +MCU_ST_STM32F4:STM32F412VEHx +MCU_ST_STM32F4:STM32F412VETx +MCU_ST_STM32F4:STM32F412VGHx +MCU_ST_STM32F4:STM32F412VGTx +MCU_ST_STM32F4:STM32F412V_E-G_Hx +MCU_ST_STM32F4:STM32F412V_E-G_Tx +MCU_ST_STM32F4:STM32F412ZEJx +MCU_ST_STM32F4:STM32F412ZETx +MCU_ST_STM32F4:STM32F412ZGJx +MCU_ST_STM32F4:STM32F412ZGTx +MCU_ST_STM32F4:STM32F412Z_E-G_Jx +MCU_ST_STM32F4:STM32F412Z_E-G_Tx +MCU_ST_STM32F4:STM32F413CGUx +MCU_ST_STM32F4:STM32F413CHUx +MCU_ST_STM32F4:STM32F413C_G-H_Ux +MCU_ST_STM32F4:STM32F413MGYx +MCU_ST_STM32F4:STM32F413MHYx +MCU_ST_STM32F4:STM32F413M_G-H_Yx +MCU_ST_STM32F4:STM32F413RGTx +MCU_ST_STM32F4:STM32F413RHTx +MCU_ST_STM32F4:STM32F413R_G-H_Tx +MCU_ST_STM32F4:STM32F413VGHx +MCU_ST_STM32F4:STM32F413VGTx +MCU_ST_STM32F4:STM32F413VHHx +MCU_ST_STM32F4:STM32F413VHTx +MCU_ST_STM32F4:STM32F413V_G-H_Hx +MCU_ST_STM32F4:STM32F413V_G-H_Tx +MCU_ST_STM32F4:STM32F413ZGJx +MCU_ST_STM32F4:STM32F413ZGTx +MCU_ST_STM32F4:STM32F413ZHJx +MCU_ST_STM32F4:STM32F413ZHTx +MCU_ST_STM32F4:STM32F413Z_G-H_Jx +MCU_ST_STM32F4:STM32F413Z_G-H_Tx +MCU_ST_STM32F4:STM32F415OGYx +MCU_ST_STM32F4:STM32F415RGTx +MCU_ST_STM32F4:STM32F415VGTx +MCU_ST_STM32F4:STM32F415ZGTx +MCU_ST_STM32F4:STM32F417IEHx +MCU_ST_STM32F4:STM32F417IETx +MCU_ST_STM32F4:STM32F417IGHx +MCU_ST_STM32F4:STM32F417IGTx +MCU_ST_STM32F4:STM32F417I_E-G_Hx +MCU_ST_STM32F4:STM32F417I_E-G_Tx +MCU_ST_STM32F4:STM32F417VETx +MCU_ST_STM32F4:STM32F417VGTx +MCU_ST_STM32F4:STM32F417V_E-G_Tx +MCU_ST_STM32F4:STM32F417ZETx +MCU_ST_STM32F4:STM32F417ZGTx +MCU_ST_STM32F4:STM32F417Z_E-G_Tx +MCU_ST_STM32F4:STM32F423CHUx +MCU_ST_STM32F4:STM32F423MHYx +MCU_ST_STM32F4:STM32F423RHTx +MCU_ST_STM32F4:STM32F423VHHx +MCU_ST_STM32F4:STM32F423VHTx +MCU_ST_STM32F4:STM32F423ZHJx +MCU_ST_STM32F4:STM32F423ZHTx +MCU_ST_STM32F4:STM32F427AGHx +MCU_ST_STM32F4:STM32F427AIHx +MCU_ST_STM32F4:STM32F427A_G-I_Hx +MCU_ST_STM32F4:STM32F427IGHx +MCU_ST_STM32F4:STM32F427IGTx +MCU_ST_STM32F4:STM32F427IIHx +MCU_ST_STM32F4:STM32F427IITx +MCU_ST_STM32F4:STM32F427I_G-I_Hx +MCU_ST_STM32F4:STM32F427I_G-I_Tx +MCU_ST_STM32F4:STM32F427VGTx +MCU_ST_STM32F4:STM32F427VITx +MCU_ST_STM32F4:STM32F427V_G-I_Tx +MCU_ST_STM32F4:STM32F427ZGTx +MCU_ST_STM32F4:STM32F427ZITx +MCU_ST_STM32F4:STM32F427Z_G-I_Tx +MCU_ST_STM32F4:STM32F429AGHx +MCU_ST_STM32F4:STM32F429AIHx +MCU_ST_STM32F4:STM32F429A_G-I_Hx +MCU_ST_STM32F4:STM32F429BETx +MCU_ST_STM32F4:STM32F429BGTx +MCU_ST_STM32F4:STM32F429BITx +MCU_ST_STM32F4:STM32F429B_E-G-I_Tx +MCU_ST_STM32F4:STM32F429IEHx +MCU_ST_STM32F4:STM32F429IETx +MCU_ST_STM32F4:STM32F429IGHx +MCU_ST_STM32F4:STM32F429IGTx +MCU_ST_STM32F4:STM32F429IIHx +MCU_ST_STM32F4:STM32F429IITx +MCU_ST_STM32F4:STM32F429I_E-G-I_Hx +MCU_ST_STM32F4:STM32F429I_E-G_Tx +MCU_ST_STM32F4:STM32F429NEHx +MCU_ST_STM32F4:STM32F429NGHx +MCU_ST_STM32F4:STM32F429NIHx +MCU_ST_STM32F4:STM32F429N_E-G_Hx +MCU_ST_STM32F4:STM32F429VETx +MCU_ST_STM32F4:STM32F429VGTx +MCU_ST_STM32F4:STM32F429VITx +MCU_ST_STM32F4:STM32F429V_E-G_Tx +MCU_ST_STM32F4:STM32F429ZETx +MCU_ST_STM32F4:STM32F429ZGTx +MCU_ST_STM32F4:STM32F429ZGYx +MCU_ST_STM32F4:STM32F429ZITx +MCU_ST_STM32F4:STM32F429ZIYx +MCU_ST_STM32F4:STM32F429Z_E-G_Tx +MCU_ST_STM32F4:STM32F437AIHx +MCU_ST_STM32F4:STM32F437IGHx +MCU_ST_STM32F4:STM32F437IGTx +MCU_ST_STM32F4:STM32F437IIHx +MCU_ST_STM32F4:STM32F437IITx +MCU_ST_STM32F4:STM32F437I_G-I_Hx +MCU_ST_STM32F4:STM32F437I_G-I_Tx +MCU_ST_STM32F4:STM32F437VGTx +MCU_ST_STM32F4:STM32F437VITx +MCU_ST_STM32F4:STM32F437V_G-I_Tx +MCU_ST_STM32F4:STM32F437ZGTx +MCU_ST_STM32F4:STM32F437ZITx +MCU_ST_STM32F4:STM32F437Z_G-I_Tx +MCU_ST_STM32F4:STM32F439AIHx +MCU_ST_STM32F4:STM32F439BGTx +MCU_ST_STM32F4:STM32F439BITx +MCU_ST_STM32F4:STM32F439B_G-I_Tx +MCU_ST_STM32F4:STM32F439IGHx +MCU_ST_STM32F4:STM32F439IGTx +MCU_ST_STM32F4:STM32F439IIHx +MCU_ST_STM32F4:STM32F439IITx +MCU_ST_STM32F4:STM32F439I_G-I_Hx +MCU_ST_STM32F4:STM32F439I_G-I_Tx +MCU_ST_STM32F4:STM32F439NGHx +MCU_ST_STM32F4:STM32F439NIHx +MCU_ST_STM32F4:STM32F439N_G-I_Hx +MCU_ST_STM32F4:STM32F439VGTx +MCU_ST_STM32F4:STM32F439VITx +MCU_ST_STM32F4:STM32F439V_G-I_Tx +MCU_ST_STM32F4:STM32F439ZGTx +MCU_ST_STM32F4:STM32F439ZGYx +MCU_ST_STM32F4:STM32F439ZITx +MCU_ST_STM32F4:STM32F439ZIYx +MCU_ST_STM32F4:STM32F439Z_G-I_Tx +MCU_ST_STM32F4:STM32F439Z_G-I_Yx +MCU_ST_STM32F4:STM32F446MCYx +MCU_ST_STM32F4:STM32F446MEYx +MCU_ST_STM32F4:STM32F446M_C-E_Yx +MCU_ST_STM32F4:STM32F446RCTx +MCU_ST_STM32F4:STM32F446RETx +MCU_ST_STM32F4:STM32F446R_C-E_Tx +MCU_ST_STM32F4:STM32F446VCTx +MCU_ST_STM32F4:STM32F446VETx +MCU_ST_STM32F4:STM32F446V_C-E_Tx +MCU_ST_STM32F4:STM32F446ZCHx +MCU_ST_STM32F4:STM32F446ZCJx +MCU_ST_STM32F4:STM32F446ZCTx +MCU_ST_STM32F4:STM32F446ZEHx +MCU_ST_STM32F4:STM32F446ZEJx +MCU_ST_STM32F4:STM32F446ZETx +MCU_ST_STM32F4:STM32F446Z_C-E_Hx +MCU_ST_STM32F4:STM32F446Z_C-E_Jx +MCU_ST_STM32F4:STM32F446Z_C-E_Tx +MCU_ST_STM32F4:STM32F469AEHx +MCU_ST_STM32F4:STM32F469AEYx +MCU_ST_STM32F4:STM32F469AGHx +MCU_ST_STM32F4:STM32F469AGYx +MCU_ST_STM32F4:STM32F469AIHx +MCU_ST_STM32F4:STM32F469AIYx +MCU_ST_STM32F4:STM32F469A_E-G-I_Hx +MCU_ST_STM32F4:STM32F469A_E-G-I_Yx +MCU_ST_STM32F4:STM32F469BETx +MCU_ST_STM32F4:STM32F469BGTx +MCU_ST_STM32F4:STM32F469BITx +MCU_ST_STM32F4:STM32F469B_E-G-I_Tx +MCU_ST_STM32F4:STM32F469IEHx +MCU_ST_STM32F4:STM32F469IETx +MCU_ST_STM32F4:STM32F469IGHx +MCU_ST_STM32F4:STM32F469IGTx +MCU_ST_STM32F4:STM32F469IIHx +MCU_ST_STM32F4:STM32F469IITx +MCU_ST_STM32F4:STM32F469I_E-G-I_Hx +MCU_ST_STM32F4:STM32F469I_E-G_Tx +MCU_ST_STM32F4:STM32F469NEHx +MCU_ST_STM32F4:STM32F469NGHx +MCU_ST_STM32F4:STM32F469NIHx +MCU_ST_STM32F4:STM32F469N_E-G_Hx +MCU_ST_STM32F4:STM32F469VETx +MCU_ST_STM32F4:STM32F469VGTx +MCU_ST_STM32F4:STM32F469VITx +MCU_ST_STM32F4:STM32F469V_E-G_Tx +MCU_ST_STM32F4:STM32F469ZETx +MCU_ST_STM32F4:STM32F469ZGTx +MCU_ST_STM32F4:STM32F469ZITx +MCU_ST_STM32F4:STM32F469Z_E-G_Tx +MCU_ST_STM32F4:STM32F479AGHx +MCU_ST_STM32F4:STM32F479AGYx +MCU_ST_STM32F4:STM32F479AIHx +MCU_ST_STM32F4:STM32F479AIYx +MCU_ST_STM32F4:STM32F479A_G-I_Hx +MCU_ST_STM32F4:STM32F479A_G-I_Yx +MCU_ST_STM32F4:STM32F479BGTx +MCU_ST_STM32F4:STM32F479BITx +MCU_ST_STM32F4:STM32F479B_G-I_Tx +MCU_ST_STM32F4:STM32F479IGHx +MCU_ST_STM32F4:STM32F479IGTx +MCU_ST_STM32F4:STM32F479IIHx +MCU_ST_STM32F4:STM32F479IITx +MCU_ST_STM32F4:STM32F479I_G-I_Hx +MCU_ST_STM32F4:STM32F479I_G-I_Tx +MCU_ST_STM32F4:STM32F479NGHx +MCU_ST_STM32F4:STM32F479NIHx +MCU_ST_STM32F4:STM32F479N_G-I_Hx +MCU_ST_STM32F4:STM32F479VGTx +MCU_ST_STM32F4:STM32F479VITx +MCU_ST_STM32F4:STM32F479V_G-I_Tx +MCU_ST_STM32F4:STM32F479ZGTx +MCU_ST_STM32F4:STM32F479ZITx +MCU_ST_STM32F4:STM32F479Z_G-I_Tx +MCU_ST_STM32F7:STM32F722ICKx +MCU_ST_STM32F7:STM32F722ICTx +MCU_ST_STM32F7:STM32F722IEKx +MCU_ST_STM32F7:STM32F722IETx +MCU_ST_STM32F7:STM32F722I_C-E_Kx +MCU_ST_STM32F7:STM32F722I_C-E_Tx +MCU_ST_STM32F7:STM32F722RCTx +MCU_ST_STM32F7:STM32F722RETx +MCU_ST_STM32F7:STM32F722R_C-E_Tx +MCU_ST_STM32F7:STM32F722VCTx +MCU_ST_STM32F7:STM32F722VETx +MCU_ST_STM32F7:STM32F722V_C-E_Tx +MCU_ST_STM32F7:STM32F722ZCTx +MCU_ST_STM32F7:STM32F722ZETx +MCU_ST_STM32F7:STM32F722Z_C-E_Tx +MCU_ST_STM32F7:STM32F723ICKx +MCU_ST_STM32F7:STM32F723ICTx +MCU_ST_STM32F7:STM32F723IEKx +MCU_ST_STM32F7:STM32F723IETx +MCU_ST_STM32F7:STM32F723I_C-E_Kx +MCU_ST_STM32F7:STM32F723I_C-E_Tx +MCU_ST_STM32F7:STM32F723VCTx +MCU_ST_STM32F7:STM32F723VCYx +MCU_ST_STM32F7:STM32F723VETx +MCU_ST_STM32F7:STM32F723VEYx +MCU_ST_STM32F7:STM32F723V_C-E_Tx +MCU_ST_STM32F7:STM32F723V_C-E_Yx +MCU_ST_STM32F7:STM32F723ZCIx +MCU_ST_STM32F7:STM32F723ZCTx +MCU_ST_STM32F7:STM32F723ZEIx +MCU_ST_STM32F7:STM32F723ZETx +MCU_ST_STM32F7:STM32F723Z_C-E_Ix +MCU_ST_STM32F7:STM32F723Z_C-E_Tx +MCU_ST_STM32F7:STM32F730I8Kx +MCU_ST_STM32F7:STM32F730R8Tx +MCU_ST_STM32F7:STM32F730V8Tx +MCU_ST_STM32F7:STM32F730Z8Tx +MCU_ST_STM32F7:STM32F732IEKx +MCU_ST_STM32F7:STM32F732IETx +MCU_ST_STM32F7:STM32F732RETx +MCU_ST_STM32F7:STM32F732VETx +MCU_ST_STM32F7:STM32F732ZETx +MCU_ST_STM32F7:STM32F733IEKx +MCU_ST_STM32F7:STM32F733IETx +MCU_ST_STM32F7:STM32F733VETx +MCU_ST_STM32F7:STM32F733VEYx +MCU_ST_STM32F7:STM32F733ZEIx +MCU_ST_STM32F7:STM32F733ZETx +MCU_ST_STM32F7:STM32F745IEKx +MCU_ST_STM32F7:STM32F745IETx +MCU_ST_STM32F7:STM32F745IGKx +MCU_ST_STM32F7:STM32F745IGTx +MCU_ST_STM32F7:STM32F745I_E-G_Kx +MCU_ST_STM32F7:STM32F745I_E-G_Tx +MCU_ST_STM32F7:STM32F745VEHx +MCU_ST_STM32F7:STM32F745VETx +MCU_ST_STM32F7:STM32F745VGHx +MCU_ST_STM32F7:STM32F745VGTx +MCU_ST_STM32F7:STM32F745V_E-G_Hx +MCU_ST_STM32F7:STM32F745V_E-G_Tx +MCU_ST_STM32F7:STM32F745ZETx +MCU_ST_STM32F7:STM32F745ZGTx +MCU_ST_STM32F7:STM32F745Z_E-G_Tx +MCU_ST_STM32F7:STM32F746BETx +MCU_ST_STM32F7:STM32F746BGTx +MCU_ST_STM32F7:STM32F746B_E-G_Tx +MCU_ST_STM32F7:STM32F746IEKx +MCU_ST_STM32F7:STM32F746IETx +MCU_ST_STM32F7:STM32F746IGKx +MCU_ST_STM32F7:STM32F746IGTx +MCU_ST_STM32F7:STM32F746I_E-G_Kx +MCU_ST_STM32F7:STM32F746NEHx +MCU_ST_STM32F7:STM32F746NGHx +MCU_ST_STM32F7:STM32F746VEHx +MCU_ST_STM32F7:STM32F746VETx +MCU_ST_STM32F7:STM32F746VGHx +MCU_ST_STM32F7:STM32F746VGTx +MCU_ST_STM32F7:STM32F746V_E-G_Hx +MCU_ST_STM32F7:STM32F746ZETx +MCU_ST_STM32F7:STM32F746ZEYx +MCU_ST_STM32F7:STM32F746ZGTx +MCU_ST_STM32F7:STM32F746ZGYx +MCU_ST_STM32F7:STM32F746Z_E-G_Yx +MCU_ST_STM32F7:STM32F750N8Hx +MCU_ST_STM32F7:STM32F750V8Tx +MCU_ST_STM32F7:STM32F750Z8Tx +MCU_ST_STM32F7:STM32F756BGTx +MCU_ST_STM32F7:STM32F756IGKx +MCU_ST_STM32F7:STM32F756IGTx +MCU_ST_STM32F7:STM32F756NGHx +MCU_ST_STM32F7:STM32F756VGHx +MCU_ST_STM32F7:STM32F756VGTx +MCU_ST_STM32F7:STM32F756ZGTx +MCU_ST_STM32F7:STM32F756ZGYx +MCU_ST_STM32F7:STM32F765BGTx +MCU_ST_STM32F7:STM32F765BITx +MCU_ST_STM32F7:STM32F765B_G-I_Tx +MCU_ST_STM32F7:STM32F765IGKx +MCU_ST_STM32F7:STM32F765IGTx +MCU_ST_STM32F7:STM32F765IIKx +MCU_ST_STM32F7:STM32F765IITx +MCU_ST_STM32F7:STM32F765I_G-I_Kx +MCU_ST_STM32F7:STM32F765I_G-I_Tx +MCU_ST_STM32F7:STM32F765NGHx +MCU_ST_STM32F7:STM32F765NIHx +MCU_ST_STM32F7:STM32F765N_G-I_Hx +MCU_ST_STM32F7:STM32F765VGHx +MCU_ST_STM32F7:STM32F765VGTx +MCU_ST_STM32F7:STM32F765VIHx +MCU_ST_STM32F7:STM32F765VITx +MCU_ST_STM32F7:STM32F765V_G-I_Hx +MCU_ST_STM32F7:STM32F765V_G-I_Tx +MCU_ST_STM32F7:STM32F765ZGTx +MCU_ST_STM32F7:STM32F765ZITx +MCU_ST_STM32F7:STM32F765Z_G-I_Tx +MCU_ST_STM32F7:STM32F767BGTx +MCU_ST_STM32F7:STM32F767BITx +MCU_ST_STM32F7:STM32F767B_G-I_Tx +MCU_ST_STM32F7:STM32F767IGKx +MCU_ST_STM32F7:STM32F767IGTx +MCU_ST_STM32F7:STM32F767IIKx +MCU_ST_STM32F7:STM32F767IITx +MCU_ST_STM32F7:STM32F767I_G-I_Kx +MCU_ST_STM32F7:STM32F767I_G-I_Tx +MCU_ST_STM32F7:STM32F767NGHx +MCU_ST_STM32F7:STM32F767NIHx +MCU_ST_STM32F7:STM32F767N_G-I_Hx +MCU_ST_STM32F7:STM32F767VGHx +MCU_ST_STM32F7:STM32F767VGTx +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 +MCU_ST_STM32F7:STM32F769BGTx +MCU_ST_STM32F7:STM32F769BITx +MCU_ST_STM32F7:STM32F769B_G-I_Tx +MCU_ST_STM32F7:STM32F769IGTx +MCU_ST_STM32F7:STM32F769IITx +MCU_ST_STM32F7:STM32F769NGHx +MCU_ST_STM32F7:STM32F769NIHx +MCU_ST_STM32F7:STM32F777BITx +MCU_ST_STM32F7:STM32F777IIKx +MCU_ST_STM32F7:STM32F777IITx +MCU_ST_STM32F7:STM32F777NIHx +MCU_ST_STM32F7:STM32F777VIHx +MCU_ST_STM32F7:STM32F777VITx +MCU_ST_STM32F7:STM32F777ZITx +MCU_ST_STM32F7:STM32F778AIYx +MCU_ST_STM32F7:STM32F779AIYx +MCU_ST_STM32F7:STM32F779BITx +MCU_ST_STM32F7:STM32F779IITx +MCU_ST_STM32F7:STM32F779NIHx +MCU_ST_STM32G0:STM32G030C6Tx +MCU_ST_STM32G0:STM32G030C8Tx +MCU_ST_STM32G0:STM32G030C_6-8_Tx +MCU_ST_STM32G0:STM32G030F6Px +MCU_ST_STM32G0:STM32G030J6Mx +MCU_ST_STM32G0:STM32G030K6Tx +MCU_ST_STM32G0:STM32G030K8Tx +MCU_ST_STM32G0:STM32G030K_6-8_Tx +MCU_ST_STM32G0:STM32G031C4Tx +MCU_ST_STM32G0:STM32G031C4Ux +MCU_ST_STM32G0:STM32G031C6Tx +MCU_ST_STM32G0:STM32G031C6Ux +MCU_ST_STM32G0:STM32G031C8Tx +MCU_ST_STM32G0:STM32G031C8Ux +MCU_ST_STM32G0:STM32G031C_4-6-8_Tx +MCU_ST_STM32G0:STM32G031C_4-6-8_Ux +MCU_ST_STM32G0:STM32G031F4Px +MCU_ST_STM32G0:STM32G031F6Px +MCU_ST_STM32G0:STM32G031F8Px +MCU_ST_STM32G0:STM32G031F_4-6-8_Px +MCU_ST_STM32G0:STM32G031G4Ux +MCU_ST_STM32G0:STM32G031G6Ux +MCU_ST_STM32G0:STM32G031G8Ux +MCU_ST_STM32G0:STM32G031G_4-6-8_Ux +MCU_ST_STM32G0:STM32G031J4Mx +MCU_ST_STM32G0:STM32G031J6Mx +MCU_ST_STM32G0:STM32G031J_4-6_Mx +MCU_ST_STM32G0:STM32G031K4Tx +MCU_ST_STM32G0:STM32G031K4Ux +MCU_ST_STM32G0:STM32G031K6Tx +MCU_ST_STM32G0:STM32G031K6Ux +MCU_ST_STM32G0:STM32G031K8Tx +MCU_ST_STM32G0:STM32G031K8Ux +MCU_ST_STM32G0:STM32G031K_4-6-8_Tx +MCU_ST_STM32G0:STM32G031K_4-6-8_Ux +MCU_ST_STM32G0:STM32G031Y8Yx +MCU_ST_STM32G0:STM32G041C6Tx +MCU_ST_STM32G0:STM32G041C6Ux +MCU_ST_STM32G0:STM32G041C8Tx +MCU_ST_STM32G0:STM32G041C8Ux +MCU_ST_STM32G0:STM32G041C_6-8_Tx +MCU_ST_STM32G0:STM32G041C_6-8_Ux +MCU_ST_STM32G0:STM32G041F6Px +MCU_ST_STM32G0:STM32G041F8Px +MCU_ST_STM32G0:STM32G041F_6-8_Px +MCU_ST_STM32G0:STM32G041G6Ux +MCU_ST_STM32G0:STM32G041G8Ux +MCU_ST_STM32G0:STM32G041G_6-8_Ux +MCU_ST_STM32G0:STM32G041J6Mx +MCU_ST_STM32G0:STM32G041K6Tx +MCU_ST_STM32G0:STM32G041K6Ux +MCU_ST_STM32G0:STM32G041K8Tx +MCU_ST_STM32G0:STM32G041K8Ux +MCU_ST_STM32G0:STM32G041K_6-8_Tx +MCU_ST_STM32G0:STM32G041K_6-8_Ux +MCU_ST_STM32G0:STM32G041Y8Yx +MCU_ST_STM32G0:STM32G050C6Tx +MCU_ST_STM32G0:STM32G050C8Tx +MCU_ST_STM32G0:STM32G050F6Px +MCU_ST_STM32G0:STM32G050K6Tx +MCU_ST_STM32G0:STM32G050K8Tx +MCU_ST_STM32G0:STM32G051C6Tx +MCU_ST_STM32G0:STM32G051C6Ux +MCU_ST_STM32G0:STM32G051C8Tx +MCU_ST_STM32G0:STM32G051C8Ux +MCU_ST_STM32G0:STM32G051C_6-8_Tx +MCU_ST_STM32G0:STM32G051C_6-8_Ux +MCU_ST_STM32G0:STM32G051F6Px +MCU_ST_STM32G0:STM32G051F8Px +MCU_ST_STM32G0:STM32G051F8Yx +MCU_ST_STM32G0:STM32G051F_6-8_Px +MCU_ST_STM32G0:STM32G051G6Ux +MCU_ST_STM32G0:STM32G051G8Ux +MCU_ST_STM32G0:STM32G051G_6-8_Ux +MCU_ST_STM32G0:STM32G051K6Tx +MCU_ST_STM32G0:STM32G051K6Ux +MCU_ST_STM32G0:STM32G051K8Tx +MCU_ST_STM32G0:STM32G051K8Ux +MCU_ST_STM32G0:STM32G051K_6-8_Tx +MCU_ST_STM32G0:STM32G051K_6-8_Ux +MCU_ST_STM32G0:STM32G061C6Tx +MCU_ST_STM32G0:STM32G061C6Ux +MCU_ST_STM32G0:STM32G061C8Tx +MCU_ST_STM32G0:STM32G061C8Ux +MCU_ST_STM32G0:STM32G061C_6-8_Tx +MCU_ST_STM32G0:STM32G061C_6-8_Ux +MCU_ST_STM32G0:STM32G061F6Px +MCU_ST_STM32G0:STM32G061F8Px +MCU_ST_STM32G0:STM32G061F8Yx +MCU_ST_STM32G0:STM32G061F_6-8_Px +MCU_ST_STM32G0:STM32G061G6Ux +MCU_ST_STM32G0:STM32G061G8Ux +MCU_ST_STM32G0:STM32G061G_6-8_Ux +MCU_ST_STM32G0:STM32G061K6Tx +MCU_ST_STM32G0:STM32G061K6Ux +MCU_ST_STM32G0:STM32G061K8Tx +MCU_ST_STM32G0:STM32G061K8Ux +MCU_ST_STM32G0:STM32G061K_6-8_Tx +MCU_ST_STM32G0:STM32G061K_6-8_Ux +MCU_ST_STM32G0:STM32G070CBTx +MCU_ST_STM32G0:STM32G070KBTx +MCU_ST_STM32G0:STM32G070RBTx +MCU_ST_STM32G0:STM32G071EBYx +MCU_ST_STM32G0:STM32G071G8UxN +MCU_ST_STM32G0:STM32G071GBUxN +MCU_ST_STM32G0:STM32G071G_8-B_UxN +MCU_ST_STM32G0:STM32G071K8TxN +MCU_ST_STM32G0:STM32G071K8UxN +MCU_ST_STM32G0:STM32G071KBTxN +MCU_ST_STM32G0:STM32G071KBUxN +MCU_ST_STM32G0:STM32G071K_8-B_TxN +MCU_ST_STM32G0:STM32G071K_8-B_UxN +MCU_ST_STM32G0:STM32G071RBIx +MCU_ST_STM32G0:STM32G081CBTx +MCU_ST_STM32G0:STM32G081CBUx +MCU_ST_STM32G0:STM32G081EBYx +MCU_ST_STM32G0:STM32G081GBUx +MCU_ST_STM32G0:STM32G081GBUxN +MCU_ST_STM32G0:STM32G081KBTx +MCU_ST_STM32G0:STM32G081KBTxN +MCU_ST_STM32G0:STM32G081KBUx +MCU_ST_STM32G0:STM32G081KBUxN +MCU_ST_STM32G0:STM32G081RBIx +MCU_ST_STM32G0:STM32G081RBTx +MCU_ST_STM32G0:STM32G0B0CETx +MCU_ST_STM32G0:STM32G0B0KETx +MCU_ST_STM32G0:STM32G0B0RETx +MCU_ST_STM32G0:STM32G0B0VETx +MCU_ST_STM32G0:STM32G0B1CBTx +MCU_ST_STM32G0:STM32G0B1CBTxN +MCU_ST_STM32G0:STM32G0B1CBUx +MCU_ST_STM32G0:STM32G0B1CBUxN +MCU_ST_STM32G0:STM32G0B1CCTx +MCU_ST_STM32G0:STM32G0B1CCTxN +MCU_ST_STM32G0:STM32G0B1CCUx +MCU_ST_STM32G0:STM32G0B1CCUxN +MCU_ST_STM32G0:STM32G0B1CETx +MCU_ST_STM32G0:STM32G0B1CETxN +MCU_ST_STM32G0:STM32G0B1CEUx +MCU_ST_STM32G0:STM32G0B1CEUxN +MCU_ST_STM32G0:STM32G0B1C_B-C-E_Tx +MCU_ST_STM32G0:STM32G0B1C_B-C-E_TxN +MCU_ST_STM32G0:STM32G0B1C_B-C-E_Ux +MCU_ST_STM32G0:STM32G0B1C_B-C-E_UxN +MCU_ST_STM32G0:STM32G0B1KBTx +MCU_ST_STM32G0:STM32G0B1KBTxN +MCU_ST_STM32G0:STM32G0B1KBUx +MCU_ST_STM32G0:STM32G0B1KBUxN +MCU_ST_STM32G0:STM32G0B1KCTx +MCU_ST_STM32G0:STM32G0B1KCTxN +MCU_ST_STM32G0:STM32G0B1KCUx +MCU_ST_STM32G0:STM32G0B1KCUxN +MCU_ST_STM32G0:STM32G0B1KETx +MCU_ST_STM32G0:STM32G0B1KETxN +MCU_ST_STM32G0:STM32G0B1KEUx +MCU_ST_STM32G0:STM32G0B1KEUxN +MCU_ST_STM32G0:STM32G0B1K_B-C-E_Tx +MCU_ST_STM32G0:STM32G0B1K_B-C-E_TxN +MCU_ST_STM32G0:STM32G0B1K_B-C-E_Ux +MCU_ST_STM32G0:STM32G0B1K_B-C-E_UxN +MCU_ST_STM32G0:STM32G0B1MBTx +MCU_ST_STM32G0:STM32G0B1MCTx +MCU_ST_STM32G0:STM32G0B1METx +MCU_ST_STM32G0:STM32G0B1M_B-C-E_Tx +MCU_ST_STM32G0:STM32G0B1NEYx +MCU_ST_STM32G0:STM32G0B1RBIxN +MCU_ST_STM32G0:STM32G0B1RBTx +MCU_ST_STM32G0:STM32G0B1RBTxN +MCU_ST_STM32G0:STM32G0B1RCIxN +MCU_ST_STM32G0:STM32G0B1RCTx +MCU_ST_STM32G0:STM32G0B1RCTxN +MCU_ST_STM32G0:STM32G0B1REIxN +MCU_ST_STM32G0:STM32G0B1RETx +MCU_ST_STM32G0:STM32G0B1RETxN +MCU_ST_STM32G0:STM32G0B1R_B-C-E_IxN +MCU_ST_STM32G0:STM32G0B1R_B-C-E_Tx +MCU_ST_STM32G0:STM32G0B1R_B-C-E_TxN +MCU_ST_STM32G0:STM32G0B1VBIx +MCU_ST_STM32G0:STM32G0B1VBTx +MCU_ST_STM32G0:STM32G0B1VCIx +MCU_ST_STM32G0:STM32G0B1VCTx +MCU_ST_STM32G0:STM32G0B1VEIx +MCU_ST_STM32G0:STM32G0B1VETx +MCU_ST_STM32G0:STM32G0B1V_B-C-E_Ix +MCU_ST_STM32G0:STM32G0B1V_B-C-E_Tx +MCU_ST_STM32G0:STM32G0C1CCTx +MCU_ST_STM32G0:STM32G0C1CCTxN +MCU_ST_STM32G0:STM32G0C1CCUx +MCU_ST_STM32G0:STM32G0C1CCUxN +MCU_ST_STM32G0:STM32G0C1CETx +MCU_ST_STM32G0:STM32G0C1CETxN +MCU_ST_STM32G0:STM32G0C1CEUx +MCU_ST_STM32G0:STM32G0C1CEUxN +MCU_ST_STM32G0:STM32G0C1C_C-E_Tx +MCU_ST_STM32G0:STM32G0C1C_C-E_TxN +MCU_ST_STM32G0:STM32G0C1C_C-E_Ux +MCU_ST_STM32G0:STM32G0C1C_C-E_UxN +MCU_ST_STM32G0:STM32G0C1KCTx +MCU_ST_STM32G0:STM32G0C1KCTxN +MCU_ST_STM32G0:STM32G0C1KCUx +MCU_ST_STM32G0:STM32G0C1KCUxN +MCU_ST_STM32G0:STM32G0C1KETx +MCU_ST_STM32G0:STM32G0C1KETxN +MCU_ST_STM32G0:STM32G0C1KEUx +MCU_ST_STM32G0:STM32G0C1KEUxN +MCU_ST_STM32G0:STM32G0C1K_C-E_Tx +MCU_ST_STM32G0:STM32G0C1K_C-E_TxN +MCU_ST_STM32G0:STM32G0C1K_C-E_Ux +MCU_ST_STM32G0:STM32G0C1K_C-E_UxN +MCU_ST_STM32G0:STM32G0C1MCTx +MCU_ST_STM32G0:STM32G0C1METx +MCU_ST_STM32G0:STM32G0C1M_C-E_Tx +MCU_ST_STM32G0:STM32G0C1NEYx +MCU_ST_STM32G0:STM32G0C1RCIxN +MCU_ST_STM32G0:STM32G0C1RCTx +MCU_ST_STM32G0:STM32G0C1RCTxN +MCU_ST_STM32G0:STM32G0C1REIxN +MCU_ST_STM32G0:STM32G0C1RETx +MCU_ST_STM32G0:STM32G0C1RETxN +MCU_ST_STM32G0:STM32G0C1R_C-E_IxN +MCU_ST_STM32G0:STM32G0C1R_C-E_Tx +MCU_ST_STM32G0:STM32G0C1R_C-E_TxN +MCU_ST_STM32G0:STM32G0C1VCIx +MCU_ST_STM32G0:STM32G0C1VCTx +MCU_ST_STM32G0:STM32G0C1VEIx +MCU_ST_STM32G0:STM32G0C1VETx +MCU_ST_STM32G0:STM32G0C1V_C-E_Ix +MCU_ST_STM32G0:STM32G0C1V_C-E_Tx +MCU_ST_STM32G4:STM32G431C6Tx +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 +MCU_ST_STM32G4:STM32G431C_6-8-B_Ux +MCU_ST_STM32G4:STM32G431K6Tx +MCU_ST_STM32G4:STM32G431K6Ux +MCU_ST_STM32G4:STM32G431K8Tx +MCU_ST_STM32G4:STM32G431K8Ux +MCU_ST_STM32G4:STM32G431KBTx +MCU_ST_STM32G4:STM32G431KBUx +MCU_ST_STM32G4:STM32G431K_6-8-B_Tx +MCU_ST_STM32G4:STM32G431K_6-8-B_Ux +MCU_ST_STM32G4:STM32G431M6Tx +MCU_ST_STM32G4:STM32G431M8Tx +MCU_ST_STM32G4:STM32G431MBTx +MCU_ST_STM32G4:STM32G431M_6-8-B_Tx +MCU_ST_STM32G4:STM32G431R6Ix +MCU_ST_STM32G4:STM32G431R6Tx +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 +MCU_ST_STM32G4:STM32G431V8Tx +MCU_ST_STM32G4:STM32G431VBTx +MCU_ST_STM32G4:STM32G431V_6-8-B_Tx +MCU_ST_STM32G4:STM32G441CBTx +MCU_ST_STM32G4:STM32G441CBUx +MCU_ST_STM32G4:STM32G441CBYx +MCU_ST_STM32G4:STM32G441KBTx +MCU_ST_STM32G4:STM32G441KBUx +MCU_ST_STM32G4:STM32G441MBTx +MCU_ST_STM32G4:STM32G441RBIx +MCU_ST_STM32G4:STM32G441RBTx +MCU_ST_STM32G4:STM32G441VBTx +MCU_ST_STM32G4:STM32G473CBTx +MCU_ST_STM32G4:STM32G473CBUx +MCU_ST_STM32G4:STM32G473CCTx +MCU_ST_STM32G4:STM32G473CCUx +MCU_ST_STM32G4:STM32G473CETx +MCU_ST_STM32G4:STM32G473CEUx +MCU_ST_STM32G4:STM32G473C_B-C-E_Tx +MCU_ST_STM32G4:STM32G473C_B-C-E_Ux +MCU_ST_STM32G4:STM32G473MBTx +MCU_ST_STM32G4:STM32G473MCTx +MCU_ST_STM32G4:STM32G473METx +MCU_ST_STM32G4:STM32G473MEYx +MCU_ST_STM32G4:STM32G473M_B-C-E_Tx +MCU_ST_STM32G4:STM32G473PBIx +MCU_ST_STM32G4:STM32G473PCIx +MCU_ST_STM32G4:STM32G473PEIx +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 +MCU_ST_STM32G4:STM32G473VCHx +MCU_ST_STM32G4:STM32G473VCTx +MCU_ST_STM32G4:STM32G473VEHx +MCU_ST_STM32G4:STM32G473VETx +MCU_ST_STM32G4:STM32G473V_B-C-E_Hx +MCU_ST_STM32G4:STM32G473V_B-C-E_Tx +MCU_ST_STM32G4:STM32G474CBTx +MCU_ST_STM32G4:STM32G474CBUx +MCU_ST_STM32G4:STM32G474CCTx +MCU_ST_STM32G4:STM32G474CCUx +MCU_ST_STM32G4:STM32G474CETx +MCU_ST_STM32G4:STM32G474CEUx +MCU_ST_STM32G4:STM32G474C_B-C-E_Tx +MCU_ST_STM32G4:STM32G474C_B-C-E_Ux +MCU_ST_STM32G4:STM32G474MBTx +MCU_ST_STM32G4:STM32G474MCTx +MCU_ST_STM32G4:STM32G474METx +MCU_ST_STM32G4:STM32G474MEYx +MCU_ST_STM32G4:STM32G474M_B-C-E_Tx +MCU_ST_STM32G4:STM32G474PBIx +MCU_ST_STM32G4:STM32G474PCIx +MCU_ST_STM32G4:STM32G474PEIx +MCU_ST_STM32G4:STM32G474P_B-C-E_Ix +MCU_ST_STM32G4:STM32G474QBTx +MCU_ST_STM32G4:STM32G474QCTx +MCU_ST_STM32G4:STM32G474QETx +MCU_ST_STM32G4:STM32G474Q_B-C-E_Tx +MCU_ST_STM32G4:STM32G474RBTx +MCU_ST_STM32G4:STM32G474RCTx +MCU_ST_STM32G4:STM32G474RETx +MCU_ST_STM32G4:STM32G474R_B-C-E_Tx +MCU_ST_STM32G4:STM32G474VBHx +MCU_ST_STM32G4:STM32G474VBTx +MCU_ST_STM32G4:STM32G474VCHx +MCU_ST_STM32G4:STM32G474VCTx +MCU_ST_STM32G4:STM32G474VEHx +MCU_ST_STM32G4:STM32G474VETx +MCU_ST_STM32G4:STM32G474V_B-C-E_Hx +MCU_ST_STM32G4:STM32G474V_B-C-E_Tx +MCU_ST_STM32G4:STM32G483CETx +MCU_ST_STM32G4:STM32G483CEUx +MCU_ST_STM32G4:STM32G483METx +MCU_ST_STM32G4:STM32G483MEYx +MCU_ST_STM32G4:STM32G483PEIx +MCU_ST_STM32G4:STM32G483QETx +MCU_ST_STM32G4:STM32G483RETx +MCU_ST_STM32G4:STM32G483VEHx +MCU_ST_STM32G4:STM32G483VETx +MCU_ST_STM32G4:STM32G484CETx +MCU_ST_STM32G4:STM32G484CEUx +MCU_ST_STM32G4:STM32G484METx +MCU_ST_STM32G4:STM32G484MEYx +MCU_ST_STM32G4:STM32G484PEIx +MCU_ST_STM32G4:STM32G484QETx +MCU_ST_STM32G4:STM32G484RETx +MCU_ST_STM32G4:STM32G484VEHx +MCU_ST_STM32G4:STM32G484VETx +MCU_ST_STM32G4:STM32G491CCTx +MCU_ST_STM32G4:STM32G491CCUx +MCU_ST_STM32G4:STM32G491CETx +MCU_ST_STM32G4:STM32G491CEUx +MCU_ST_STM32G4:STM32G491C_C-E_Tx +MCU_ST_STM32G4:STM32G491C_C-E_Ux +MCU_ST_STM32G4:STM32G491KCUx +MCU_ST_STM32G4:STM32G491KEUx +MCU_ST_STM32G4:STM32G491K_C-E_Ux +MCU_ST_STM32G4:STM32G491MCSx +MCU_ST_STM32G4:STM32G491MCTx +MCU_ST_STM32G4:STM32G491MESx +MCU_ST_STM32G4:STM32G491METx +MCU_ST_STM32G4:STM32G491M_C-E_Sx +MCU_ST_STM32G4:STM32G491M_C-E_Tx +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 +MCU_ST_STM32G4:STM32G491VCTx +MCU_ST_STM32G4:STM32G491VETx +MCU_ST_STM32G4:STM32G491V_C-E_Tx +MCU_ST_STM32G4:STM32G4A1CETx +MCU_ST_STM32G4:STM32G4A1CEUx +MCU_ST_STM32G4:STM32G4A1KEUx +MCU_ST_STM32G4:STM32G4A1MESx +MCU_ST_STM32G4:STM32G4A1METx +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 +MCU_ST_STM32H7:STM32H723VGTx +MCU_ST_STM32H7:STM32H723ZEIx +MCU_ST_STM32H7:STM32H723ZETx +MCU_ST_STM32H7:STM32H723ZGIx +MCU_ST_STM32H7:STM32H723ZGTx +MCU_ST_STM32H7:STM32H725AEIx +MCU_ST_STM32H7:STM32H725AGIx +MCU_ST_STM32H7:STM32H725IEKx +MCU_ST_STM32H7:STM32H725IETx +MCU_ST_STM32H7:STM32H725IGKx +MCU_ST_STM32H7:STM32H725IGTx +MCU_ST_STM32H7:STM32H725REVx +MCU_ST_STM32H7:STM32H725RGVx +MCU_ST_STM32H7:STM32H725VEHx +MCU_ST_STM32H7:STM32H725VETx +MCU_ST_STM32H7:STM32H725VGHx +MCU_ST_STM32H7:STM32H725VGTx +MCU_ST_STM32H7:STM32H725VGYx +MCU_ST_STM32H7:STM32H725ZETx +MCU_ST_STM32H7:STM32H725ZGTx +MCU_ST_STM32H7:STM32H730ABIxQ +MCU_ST_STM32H7:STM32H730IBKxQ +MCU_ST_STM32H7:STM32H730IBTxQ +MCU_ST_STM32H7:STM32H730VBHx +MCU_ST_STM32H7:STM32H730VBTx +MCU_ST_STM32H7:STM32H730ZBIx +MCU_ST_STM32H7:STM32H730ZBTx +MCU_ST_STM32H7:STM32H733VGHx +MCU_ST_STM32H7:STM32H733VGTx +MCU_ST_STM32H7:STM32H733ZGIx +MCU_ST_STM32H7:STM32H733ZGTx +MCU_ST_STM32H7:STM32H735AGIx +MCU_ST_STM32H7:STM32H735IGKx +MCU_ST_STM32H7:STM32H735IGTx +MCU_ST_STM32H7:STM32H735RGVx +MCU_ST_STM32H7:STM32H735VGHx +MCU_ST_STM32H7:STM32H735VGTx +MCU_ST_STM32H7:STM32H735VGYx +MCU_ST_STM32H7:STM32H735ZGTx +MCU_ST_STM32H7:STM32H742AGIx +MCU_ST_STM32H7:STM32H742AIIx +MCU_ST_STM32H7:STM32H742A_G-I_Ix +MCU_ST_STM32H7:STM32H742BGTx +MCU_ST_STM32H7:STM32H742BITx +MCU_ST_STM32H7:STM32H742B_G-I_Tx +MCU_ST_STM32H7:STM32H742IGKx +MCU_ST_STM32H7:STM32H742IGTx +MCU_ST_STM32H7:STM32H742IIKx +MCU_ST_STM32H7:STM32H742IITx +MCU_ST_STM32H7:STM32H742I_G-I_Kx +MCU_ST_STM32H7:STM32H742I_G-I_Tx +MCU_ST_STM32H7:STM32H742VGHx +MCU_ST_STM32H7:STM32H742VGTx +MCU_ST_STM32H7:STM32H742VIHx +MCU_ST_STM32H7:STM32H742VITx +MCU_ST_STM32H7:STM32H742V_G-I_Hx +MCU_ST_STM32H7:STM32H742V_G-I_Tx +MCU_ST_STM32H7:STM32H742XGHx +MCU_ST_STM32H7:STM32H742XIHx +MCU_ST_STM32H7:STM32H742X_G-I_Hx +MCU_ST_STM32H7:STM32H742ZGTx +MCU_ST_STM32H7:STM32H742ZITx +MCU_ST_STM32H7:STM32H742Z_G-I_Tx +MCU_ST_STM32H7:STM32H743AGIx +MCU_ST_STM32H7:STM32H743AIIx +MCU_ST_STM32H7:STM32H743A_G-I_Ix +MCU_ST_STM32H7:STM32H743BGTx +MCU_ST_STM32H7:STM32H743BITx +MCU_ST_STM32H7:STM32H743IGKx +MCU_ST_STM32H7:STM32H743IGTx +MCU_ST_STM32H7:STM32H743IIKx +MCU_ST_STM32H7:STM32H743IITx +MCU_ST_STM32H7:STM32H743VGHx +MCU_ST_STM32H7:STM32H743VGTx +MCU_ST_STM32H7:STM32H743VIHx +MCU_ST_STM32H7:STM32H743VITx +MCU_ST_STM32H7:STM32H743V_G-I_Hx +MCU_ST_STM32H7:STM32H743XGHx +MCU_ST_STM32H7:STM32H743XIHx +MCU_ST_STM32H7:STM32H743ZGTx +MCU_ST_STM32H7:STM32H743ZITx +MCU_ST_STM32H7:STM32H745BGTx +MCU_ST_STM32H7:STM32H745BITx +MCU_ST_STM32H7:STM32H745IGKx +MCU_ST_STM32H7:STM32H745IGTx +MCU_ST_STM32H7:STM32H745IIKx +MCU_ST_STM32H7:STM32H745IITx +MCU_ST_STM32H7:STM32H745XGHx +MCU_ST_STM32H7:STM32H745XIHx +MCU_ST_STM32H7:STM32H745ZGTx +MCU_ST_STM32H7:STM32H745ZITx +MCU_ST_STM32H7:STM32H747AGIx +MCU_ST_STM32H7:STM32H747AIIx +MCU_ST_STM32H7:STM32H747A_G-I_Ix +MCU_ST_STM32H7:STM32H747BGTx +MCU_ST_STM32H7:STM32H747BITx +MCU_ST_STM32H7:STM32H747IGTx +MCU_ST_STM32H7:STM32H747IITx +MCU_ST_STM32H7:STM32H747XGHx +MCU_ST_STM32H7:STM32H747XIHx +MCU_ST_STM32H7:STM32H747ZIYx +MCU_ST_STM32H7:STM32H750IBKx +MCU_ST_STM32H7:STM32H750IBTx +MCU_ST_STM32H7:STM32H750VBTx +MCU_ST_STM32H7:STM32H750XBHx +MCU_ST_STM32H7:STM32H750ZBTx +MCU_ST_STM32H7:STM32H753AIIx +MCU_ST_STM32H7:STM32H753BITx +MCU_ST_STM32H7:STM32H753IIKx +MCU_ST_STM32H7:STM32H753IITx +MCU_ST_STM32H7:STM32H753VIHx +MCU_ST_STM32H7:STM32H753VITx +MCU_ST_STM32H7:STM32H753XIHx +MCU_ST_STM32H7:STM32H753ZITx +MCU_ST_STM32H7:STM32H755BITx +MCU_ST_STM32H7:STM32H755IIKx +MCU_ST_STM32H7:STM32H755IITx +MCU_ST_STM32H7:STM32H755XIHx +MCU_ST_STM32H7:STM32H755ZITx +MCU_ST_STM32H7:STM32H757AIIx +MCU_ST_STM32H7:STM32H757BITx +MCU_ST_STM32H7:STM32H757IITx +MCU_ST_STM32H7:STM32H757XIHx +MCU_ST_STM32H7:STM32H757ZIYx +MCU_ST_STM32H7:STM32H7A3AGIxQ +MCU_ST_STM32H7:STM32H7A3AIIxQ +MCU_ST_STM32H7:STM32H7A3A_G-I_IxQ +MCU_ST_STM32H7:STM32H7A3IGKx +MCU_ST_STM32H7:STM32H7A3IGKxQ +MCU_ST_STM32H7:STM32H7A3IGTx +MCU_ST_STM32H7:STM32H7A3IGTxQ +MCU_ST_STM32H7:STM32H7A3IIKx +MCU_ST_STM32H7:STM32H7A3IIKxQ +MCU_ST_STM32H7:STM32H7A3IITx +MCU_ST_STM32H7:STM32H7A3IITxQ +MCU_ST_STM32H7:STM32H7A3I_G-I_Kx +MCU_ST_STM32H7:STM32H7A3I_G-I_KxQ +MCU_ST_STM32H7:STM32H7A3I_G-I_Tx +MCU_ST_STM32H7:STM32H7A3I_G-I_TxQ +MCU_ST_STM32H7:STM32H7A3LGHxQ +MCU_ST_STM32H7:STM32H7A3LIHxQ +MCU_ST_STM32H7:STM32H7A3L_G-I_HxQ +MCU_ST_STM32H7:STM32H7A3NGHx +MCU_ST_STM32H7:STM32H7A3NIHx +MCU_ST_STM32H7:STM32H7A3N_G-I_Hx +MCU_ST_STM32H7:STM32H7A3QIYxQ +MCU_ST_STM32H7:STM32H7A3RGTx +MCU_ST_STM32H7:STM32H7A3RITx +MCU_ST_STM32H7:STM32H7A3R_G-I_Tx +MCU_ST_STM32H7:STM32H7A3VGHx +MCU_ST_STM32H7:STM32H7A3VGHxQ +MCU_ST_STM32H7:STM32H7A3VGTx +MCU_ST_STM32H7:STM32H7A3VGTxQ +MCU_ST_STM32H7:STM32H7A3VIHx +MCU_ST_STM32H7:STM32H7A3VIHxQ +MCU_ST_STM32H7:STM32H7A3VITx +MCU_ST_STM32H7:STM32H7A3VITxQ +MCU_ST_STM32H7:STM32H7A3V_G-I_Hx +MCU_ST_STM32H7:STM32H7A3V_G-I_HxQ +MCU_ST_STM32H7:STM32H7A3V_G-I_Tx +MCU_ST_STM32H7:STM32H7A3V_G-I_TxQ +MCU_ST_STM32H7:STM32H7A3ZGTx +MCU_ST_STM32H7:STM32H7A3ZGTxQ +MCU_ST_STM32H7:STM32H7A3ZITx +MCU_ST_STM32H7:STM32H7A3ZITxQ +MCU_ST_STM32H7:STM32H7A3Z_G-I_Tx +MCU_ST_STM32H7:STM32H7A3Z_G-I_TxQ +MCU_ST_STM32H7:STM32H7B0ABIxQ +MCU_ST_STM32H7:STM32H7B0IBKxQ +MCU_ST_STM32H7:STM32H7B0IBTx +MCU_ST_STM32H7:STM32H7B0RBTx +MCU_ST_STM32H7:STM32H7B0VBTx +MCU_ST_STM32H7:STM32H7B0ZBTx +MCU_ST_STM32H7:STM32H7B3AIIxQ +MCU_ST_STM32H7:STM32H7B3IIKx +MCU_ST_STM32H7:STM32H7B3IIKxQ +MCU_ST_STM32H7:STM32H7B3IITx +MCU_ST_STM32H7:STM32H7B3IITxQ +MCU_ST_STM32H7:STM32H7B3LIHxQ +MCU_ST_STM32H7:STM32H7B3NIHx +MCU_ST_STM32H7:STM32H7B3QIYxQ +MCU_ST_STM32H7:STM32H7B3RITx +MCU_ST_STM32H7:STM32H7B3VIHx +MCU_ST_STM32H7:STM32H7B3VIHxQ +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 +MCU_ST_STM32L0:STM32L010K8Tx +MCU_ST_STM32L0:STM32L010R8Tx +MCU_ST_STM32L0:STM32L010RBTx +MCU_ST_STM32L0:STM32L011D3Px +MCU_ST_STM32L0:STM32L011D4Px +MCU_ST_STM32L0:STM32L011D_3-4_Px +MCU_ST_STM32L0:STM32L011E3Yx +MCU_ST_STM32L0:STM32L011E4Yx +MCU_ST_STM32L0:STM32L011E_3-4_Yx +MCU_ST_STM32L0:STM32L011F3Px +MCU_ST_STM32L0:STM32L011F3Ux +MCU_ST_STM32L0:STM32L011F4Px +MCU_ST_STM32L0:STM32L011F4Ux +MCU_ST_STM32L0:STM32L011F_3-4_Px +MCU_ST_STM32L0:STM32L011F_3-4_Ux +MCU_ST_STM32L0:STM32L011G3Ux +MCU_ST_STM32L0:STM32L011G4Ux +MCU_ST_STM32L0:STM32L011G_3-4_Ux +MCU_ST_STM32L0:STM32L011K3Tx +MCU_ST_STM32L0:STM32L011K3Ux +MCU_ST_STM32L0:STM32L011K4Tx +MCU_ST_STM32L0:STM32L011K4Ux +MCU_ST_STM32L0:STM32L011K_3-4_Tx +MCU_ST_STM32L0:STM32L011K_3-4_Ux +MCU_ST_STM32L0:STM32L021D4Px +MCU_ST_STM32L0:STM32L021F4Px +MCU_ST_STM32L0:STM32L021F4Ux +MCU_ST_STM32L0:STM32L021G4Ux +MCU_ST_STM32L0:STM32L021K4Tx +MCU_ST_STM32L0:STM32L021K4Ux +MCU_ST_STM32L0:STM32L031C4Tx +MCU_ST_STM32L0:STM32L031C4Ux +MCU_ST_STM32L0:STM32L031C6Tx +MCU_ST_STM32L0:STM32L031C6Ux +MCU_ST_STM32L0:STM32L031C_4-6_Tx +MCU_ST_STM32L0:STM32L031C_4-6_Ux +MCU_ST_STM32L0:STM32L031E4Yx +MCU_ST_STM32L0:STM32L031E6Yx +MCU_ST_STM32L0:STM32L031E_4-6_Yx +MCU_ST_STM32L0:STM32L031F4Px +MCU_ST_STM32L0:STM32L031F6Px +MCU_ST_STM32L0:STM32L031F_4-6_Px +MCU_ST_STM32L0:STM32L031G4Ux +MCU_ST_STM32L0:STM32L031G6Ux +MCU_ST_STM32L0:STM32L031G6UxS +MCU_ST_STM32L0:STM32L031G_4-6_Ux +MCU_ST_STM32L0:STM32L031K4Tx +MCU_ST_STM32L0:STM32L031K4Ux +MCU_ST_STM32L0:STM32L031K6Tx +MCU_ST_STM32L0:STM32L031K6Ux +MCU_ST_STM32L0:STM32L031K_4-6_Tx +MCU_ST_STM32L0:STM32L031K_4-6_Ux +MCU_ST_STM32L0:STM32L041C4Tx +MCU_ST_STM32L0:STM32L041C6Tx +MCU_ST_STM32L0:STM32L041C6Ux +MCU_ST_STM32L0:STM32L041C_4-6_Tx +MCU_ST_STM32L0:STM32L041E6Yx +MCU_ST_STM32L0:STM32L041F6Px +MCU_ST_STM32L0:STM32L041G6Ux +MCU_ST_STM32L0:STM32L041G6UxS +MCU_ST_STM32L0:STM32L041K6Tx +MCU_ST_STM32L0:STM32L041K6Ux +MCU_ST_STM32L0:STM32L051C6Tx +MCU_ST_STM32L0:STM32L051C6Ux +MCU_ST_STM32L0:STM32L051C8Tx +MCU_ST_STM32L0:STM32L051C8Ux +MCU_ST_STM32L0:STM32L051C_6-8_Tx +MCU_ST_STM32L0:STM32L051C_6-8_Ux +MCU_ST_STM32L0:STM32L051K6Tx +MCU_ST_STM32L0:STM32L051K6Ux +MCU_ST_STM32L0:STM32L051K8Tx +MCU_ST_STM32L0:STM32L051K8Ux +MCU_ST_STM32L0:STM32L051K_6-8_Tx +MCU_ST_STM32L0:STM32L051K_6-8_Ux +MCU_ST_STM32L0:STM32L051R6Hx +MCU_ST_STM32L0:STM32L051R6Tx +MCU_ST_STM32L0:STM32L051R8Hx +MCU_ST_STM32L0:STM32L051R8Tx +MCU_ST_STM32L0:STM32L051R_6-8_Hx +MCU_ST_STM32L0:STM32L051R_6-8_Tx +MCU_ST_STM32L0:STM32L051T6Yx +MCU_ST_STM32L0:STM32L051T8Yx +MCU_ST_STM32L0:STM32L051T_6-8_Yx +MCU_ST_STM32L0:STM32L052C6Tx +MCU_ST_STM32L0:STM32L052C6Ux +MCU_ST_STM32L0:STM32L052C8Tx +MCU_ST_STM32L0:STM32L052C8Ux +MCU_ST_STM32L0:STM32L052C_6-8_Tx +MCU_ST_STM32L0:STM32L052C_6-8_Ux +MCU_ST_STM32L0:STM32L052K6Tx +MCU_ST_STM32L0:STM32L052K6Ux +MCU_ST_STM32L0:STM32L052K8Tx +MCU_ST_STM32L0:STM32L052K8Ux +MCU_ST_STM32L0:STM32L052K_6-8_Tx +MCU_ST_STM32L0:STM32L052K_6-8_Ux +MCU_ST_STM32L0:STM32L052R6Hx +MCU_ST_STM32L0:STM32L052R6Tx +MCU_ST_STM32L0:STM32L052R8Hx +MCU_ST_STM32L0:STM32L052R8Tx +MCU_ST_STM32L0:STM32L052R_6-8_Hx +MCU_ST_STM32L0:STM32L052R_6-8_Tx +MCU_ST_STM32L0:STM32L052T6Yx +MCU_ST_STM32L0:STM32L052T8Fx +MCU_ST_STM32L0:STM32L052T8Yx +MCU_ST_STM32L0:STM32L052T_6-8_Yx +MCU_ST_STM32L0:STM32L053C6Tx +MCU_ST_STM32L0:STM32L053C6Ux +MCU_ST_STM32L0:STM32L053C8Tx +MCU_ST_STM32L0:STM32L053C8Ux +MCU_ST_STM32L0:STM32L053C_6-8_Tx +MCU_ST_STM32L0:STM32L053C_6-8_Ux +MCU_ST_STM32L0:STM32L053R6Hx +MCU_ST_STM32L0:STM32L053R6Tx +MCU_ST_STM32L0:STM32L053R8Hx +MCU_ST_STM32L0:STM32L053R8Tx +MCU_ST_STM32L0:STM32L053R_6-8_Hx +MCU_ST_STM32L0:STM32L053R_6-8_Tx +MCU_ST_STM32L0:STM32L062C8Ux +MCU_ST_STM32L0:STM32L062K8Tx +MCU_ST_STM32L0:STM32L062K8Ux +MCU_ST_STM32L0:STM32L063C8Tx +MCU_ST_STM32L0:STM32L063C8Ux +MCU_ST_STM32L0:STM32L063R8Tx +MCU_ST_STM32L0:STM32L071C8Tx +MCU_ST_STM32L0:STM32L071C8Ux +MCU_ST_STM32L0:STM32L071CBTx +MCU_ST_STM32L0:STM32L071CBUx +MCU_ST_STM32L0:STM32L071CBYx +MCU_ST_STM32L0:STM32L071CZTx +MCU_ST_STM32L0:STM32L071CZUx +MCU_ST_STM32L0:STM32L071CZYx +MCU_ST_STM32L0:STM32L071C_B-Z_Tx +MCU_ST_STM32L0:STM32L071C_B-Z_Ux +MCU_ST_STM32L0:STM32L071C_B-Z_Yx +MCU_ST_STM32L0:STM32L071K8Ux +MCU_ST_STM32L0:STM32L071KBTx +MCU_ST_STM32L0:STM32L071KBUx +MCU_ST_STM32L0:STM32L071KZTx +MCU_ST_STM32L0:STM32L071KZUx +MCU_ST_STM32L0:STM32L071K_B-Z_Tx +MCU_ST_STM32L0:STM32L071K_B-Z_Ux +MCU_ST_STM32L0:STM32L071RBHx +MCU_ST_STM32L0:STM32L071RBTx +MCU_ST_STM32L0:STM32L071RZHx +MCU_ST_STM32L0:STM32L071RZTx +MCU_ST_STM32L0:STM32L071R_B-Z_Hx +MCU_ST_STM32L0:STM32L071R_B-Z_Tx +MCU_ST_STM32L0:STM32L071V8Ix +MCU_ST_STM32L0:STM32L071V8Tx +MCU_ST_STM32L0:STM32L071VBIx +MCU_ST_STM32L0:STM32L071VBTx +MCU_ST_STM32L0:STM32L071VZIx +MCU_ST_STM32L0:STM32L071VZTx +MCU_ST_STM32L0:STM32L071V_B-Z_Ix +MCU_ST_STM32L0:STM32L071V_B-Z_Tx +MCU_ST_STM32L0:STM32L072CBTx +MCU_ST_STM32L0:STM32L072CBUx +MCU_ST_STM32L0:STM32L072CBYx +MCU_ST_STM32L0:STM32L072CZEx +MCU_ST_STM32L0:STM32L072CZTx +MCU_ST_STM32L0:STM32L072CZUx +MCU_ST_STM32L0:STM32L072CZYx +MCU_ST_STM32L0:STM32L072C_B-Z_Tx +MCU_ST_STM32L0:STM32L072C_B-Z_Ux +MCU_ST_STM32L0:STM32L072C_B-Z_Yx +MCU_ST_STM32L0:STM32L072KBTx +MCU_ST_STM32L0:STM32L072KBUx +MCU_ST_STM32L0:STM32L072KZTx +MCU_ST_STM32L0:STM32L072KZUx +MCU_ST_STM32L0:STM32L072K_B-Z_Tx +MCU_ST_STM32L0:STM32L072K_B-Z_Ux +MCU_ST_STM32L0:STM32L072RBHx +MCU_ST_STM32L0:STM32L072RBIx +MCU_ST_STM32L0:STM32L072RBTx +MCU_ST_STM32L0:STM32L072RZHx +MCU_ST_STM32L0:STM32L072RZIx +MCU_ST_STM32L0:STM32L072RZTx +MCU_ST_STM32L0:STM32L072R_B-Z_Hx +MCU_ST_STM32L0:STM32L072R_B-Z_Ix +MCU_ST_STM32L0:STM32L072R_B-Z_Tx +MCU_ST_STM32L0:STM32L072V8Ix +MCU_ST_STM32L0:STM32L072V8Tx +MCU_ST_STM32L0:STM32L072VBIx +MCU_ST_STM32L0:STM32L072VBTx +MCU_ST_STM32L0:STM32L072VZIx +MCU_ST_STM32L0:STM32L072VZTx +MCU_ST_STM32L0:STM32L072V_B-Z_Ix +MCU_ST_STM32L0:STM32L072V_B-Z_Tx +MCU_ST_STM32L0:STM32L073CBTx +MCU_ST_STM32L0:STM32L073CBUx +MCU_ST_STM32L0:STM32L073CZTx +MCU_ST_STM32L0:STM32L073CZUx +MCU_ST_STM32L0:STM32L073CZYx +MCU_ST_STM32L0:STM32L073C_B-Z_Tx +MCU_ST_STM32L0:STM32L073C_B-Z_Ux +MCU_ST_STM32L0:STM32L073RBHx +MCU_ST_STM32L0:STM32L073RBTx +MCU_ST_STM32L0:STM32L073RZHx +MCU_ST_STM32L0:STM32L073RZIx +MCU_ST_STM32L0:STM32L073RZTx +MCU_ST_STM32L0:STM32L073R_B-Z_Hx +MCU_ST_STM32L0:STM32L073R_B-Z_Tx +MCU_ST_STM32L0:STM32L073V8Ix +MCU_ST_STM32L0:STM32L073V8Tx +MCU_ST_STM32L0:STM32L073VBIx +MCU_ST_STM32L0:STM32L073VBTx +MCU_ST_STM32L0:STM32L073VZIx +MCU_ST_STM32L0:STM32L073VZTx +MCU_ST_STM32L0:STM32L073V_B-Z_Ix +MCU_ST_STM32L0:STM32L073V_B-Z_Tx +MCU_ST_STM32L0:STM32L081CBTx +MCU_ST_STM32L0:STM32L081CZTx +MCU_ST_STM32L0:STM32L081CZUx +MCU_ST_STM32L0:STM32L081C_B-Z_Tx +MCU_ST_STM32L0:STM32L081KZTx +MCU_ST_STM32L0:STM32L081KZUx +MCU_ST_STM32L0:STM32L082CZUx +MCU_ST_STM32L0:STM32L082CZYx +MCU_ST_STM32L0:STM32L082KBTx +MCU_ST_STM32L0:STM32L082KBUx +MCU_ST_STM32L0:STM32L082KZTx +MCU_ST_STM32L0:STM32L082KZUx +MCU_ST_STM32L0:STM32L082K_B-Z_Tx +MCU_ST_STM32L0:STM32L082K_B-Z_Ux +MCU_ST_STM32L0:STM32L083CBTx +MCU_ST_STM32L0:STM32L083CZTx +MCU_ST_STM32L0:STM32L083CZUx +MCU_ST_STM32L0:STM32L083C_B-Z_Tx +MCU_ST_STM32L0:STM32L083RBHx +MCU_ST_STM32L0:STM32L083RBTx +MCU_ST_STM32L0:STM32L083RZHx +MCU_ST_STM32L0:STM32L083RZTx +MCU_ST_STM32L0:STM32L083R_B-Z_Hx +MCU_ST_STM32L0:STM32L083R_B-Z_Tx +MCU_ST_STM32L0:STM32L083V8Ix +MCU_ST_STM32L0:STM32L083V8Tx +MCU_ST_STM32L0:STM32L083VBIx +MCU_ST_STM32L0:STM32L083VBTx +MCU_ST_STM32L0:STM32L083VZIx +MCU_ST_STM32L0:STM32L083VZTx +MCU_ST_STM32L0:STM32L083V_B-Z_Ix +MCU_ST_STM32L0:STM32L083V_B-Z_Tx +MCU_ST_STM32L1:STM32L100C6Ux +MCU_ST_STM32L1:STM32L100C6UxA +MCU_ST_STM32L1:STM32L100R8Tx +MCU_ST_STM32L1:STM32L100R8TxA +MCU_ST_STM32L1:STM32L100RBTx +MCU_ST_STM32L1:STM32L100RBTxA +MCU_ST_STM32L1:STM32L100RCTx +MCU_ST_STM32L1:STM32L100R_8-B_Tx +MCU_ST_STM32L1:STM32L100R_8-B_TxA +MCU_ST_STM32L1:STM32L151C6Tx +MCU_ST_STM32L1:STM32L151C6TxA +MCU_ST_STM32L1:STM32L151C6Ux +MCU_ST_STM32L1:STM32L151C6UxA +MCU_ST_STM32L1:STM32L151C8Tx +MCU_ST_STM32L1:STM32L151C8TxA +MCU_ST_STM32L1:STM32L151C8Ux +MCU_ST_STM32L1:STM32L151C8UxA +MCU_ST_STM32L1:STM32L151CBTx +MCU_ST_STM32L1:STM32L151CBTxA +MCU_ST_STM32L1:STM32L151CBUx +MCU_ST_STM32L1:STM32L151CBUxA +MCU_ST_STM32L1:STM32L151CCTx +MCU_ST_STM32L1:STM32L151CCUx +MCU_ST_STM32L1:STM32L151C_6-8-B_Tx +MCU_ST_STM32L1:STM32L151C_6-8-B_TxA +MCU_ST_STM32L1:STM32L151C_6-8-B_Ux +MCU_ST_STM32L1:STM32L151C_6-8-B_UxA +MCU_ST_STM32L1:STM32L151QCHx +MCU_ST_STM32L1:STM32L151QDHx +MCU_ST_STM32L1:STM32L151QEHx +MCU_ST_STM32L1:STM32L151R6Hx +MCU_ST_STM32L1:STM32L151R6HxA +MCU_ST_STM32L1:STM32L151R6Tx +MCU_ST_STM32L1:STM32L151R6TxA +MCU_ST_STM32L1:STM32L151R8Hx +MCU_ST_STM32L1:STM32L151R8HxA +MCU_ST_STM32L1:STM32L151R8Tx +MCU_ST_STM32L1:STM32L151R8TxA +MCU_ST_STM32L1:STM32L151RBHx +MCU_ST_STM32L1:STM32L151RBHxA +MCU_ST_STM32L1:STM32L151RBTx +MCU_ST_STM32L1:STM32L151RBTxA +MCU_ST_STM32L1:STM32L151RCTx +MCU_ST_STM32L1:STM32L151RCTxA +MCU_ST_STM32L1:STM32L151RCYx +MCU_ST_STM32L1:STM32L151RDTx +MCU_ST_STM32L1:STM32L151RDYx +MCU_ST_STM32L1:STM32L151RETx +MCU_ST_STM32L1:STM32L151R_6-8-B_Hx +MCU_ST_STM32L1:STM32L151R_6-8-B_HxA +MCU_ST_STM32L1:STM32L151R_6-8-B_Tx +MCU_ST_STM32L1:STM32L151R_6-8-B_TxA +MCU_ST_STM32L1:STM32L151UCYx +MCU_ST_STM32L1:STM32L151V8Hx +MCU_ST_STM32L1:STM32L151V8HxA +MCU_ST_STM32L1:STM32L151V8Tx +MCU_ST_STM32L1:STM32L151V8TxA +MCU_ST_STM32L1:STM32L151VBHx +MCU_ST_STM32L1:STM32L151VBHxA +MCU_ST_STM32L1:STM32L151VBTx +MCU_ST_STM32L1:STM32L151VBTxA +MCU_ST_STM32L1:STM32L151VCHx +MCU_ST_STM32L1:STM32L151VCTx +MCU_ST_STM32L1:STM32L151VCTxA +MCU_ST_STM32L1:STM32L151VDTx +MCU_ST_STM32L1:STM32L151VDTxX +MCU_ST_STM32L1:STM32L151VDYxX +MCU_ST_STM32L1:STM32L151VETx +MCU_ST_STM32L1:STM32L151VEYx +MCU_ST_STM32L1:STM32L151V_8-B_Hx +MCU_ST_STM32L1:STM32L151V_8-B_HxA +MCU_ST_STM32L1:STM32L151V_8-B_Tx +MCU_ST_STM32L1:STM32L151V_8-B_TxA +MCU_ST_STM32L1:STM32L151ZCTx +MCU_ST_STM32L1:STM32L151ZDTx +MCU_ST_STM32L1:STM32L151ZETx +MCU_ST_STM32L1:STM32L152C6Tx +MCU_ST_STM32L1:STM32L152C6TxA +MCU_ST_STM32L1:STM32L152C6Ux +MCU_ST_STM32L1:STM32L152C6UxA +MCU_ST_STM32L1:STM32L152C8Tx +MCU_ST_STM32L1:STM32L152C8TxA +MCU_ST_STM32L1:STM32L152C8Ux +MCU_ST_STM32L1:STM32L152C8UxA +MCU_ST_STM32L1:STM32L152CBTx +MCU_ST_STM32L1:STM32L152CBTxA +MCU_ST_STM32L1:STM32L152CBUx +MCU_ST_STM32L1:STM32L152CBUxA +MCU_ST_STM32L1:STM32L152CCTx +MCU_ST_STM32L1:STM32L152CCUx +MCU_ST_STM32L1:STM32L152C_6-8-B_Tx +MCU_ST_STM32L1:STM32L152C_6-8-B_TxA +MCU_ST_STM32L1:STM32L152C_6-8-B_Ux +MCU_ST_STM32L1:STM32L152C_6-8-B_UxA +MCU_ST_STM32L1:STM32L152QCHx +MCU_ST_STM32L1:STM32L152QDHx +MCU_ST_STM32L1:STM32L152QEHx +MCU_ST_STM32L1:STM32L152R6Hx +MCU_ST_STM32L1:STM32L152R6HxA +MCU_ST_STM32L1:STM32L152R6Tx +MCU_ST_STM32L1:STM32L152R6TxA +MCU_ST_STM32L1:STM32L152R8Hx +MCU_ST_STM32L1:STM32L152R8HxA +MCU_ST_STM32L1:STM32L152R8Tx +MCU_ST_STM32L1:STM32L152R8TxA +MCU_ST_STM32L1:STM32L152RBHx +MCU_ST_STM32L1:STM32L152RBHxA +MCU_ST_STM32L1:STM32L152RBTx +MCU_ST_STM32L1:STM32L152RBTxA +MCU_ST_STM32L1:STM32L152RCTx +MCU_ST_STM32L1:STM32L152RCTxA +MCU_ST_STM32L1:STM32L152RDTx +MCU_ST_STM32L1:STM32L152RDYx +MCU_ST_STM32L1:STM32L152RETx +MCU_ST_STM32L1:STM32L152R_6-8-B_Hx +MCU_ST_STM32L1:STM32L152R_6-8-B_HxA +MCU_ST_STM32L1:STM32L152R_6-8-B_Tx +MCU_ST_STM32L1:STM32L152R_6-8-B_TxA +MCU_ST_STM32L1:STM32L152UCYx +MCU_ST_STM32L1:STM32L152V8Hx +MCU_ST_STM32L1:STM32L152V8HxA +MCU_ST_STM32L1:STM32L152V8Tx +MCU_ST_STM32L1:STM32L152V8TxA +MCU_ST_STM32L1:STM32L152VBHx +MCU_ST_STM32L1:STM32L152VBHxA +MCU_ST_STM32L1:STM32L152VBTx +MCU_ST_STM32L1:STM32L152VBTxA +MCU_ST_STM32L1:STM32L152VCHx +MCU_ST_STM32L1:STM32L152VCTx +MCU_ST_STM32L1:STM32L152VCTxA +MCU_ST_STM32L1:STM32L152VDTx +MCU_ST_STM32L1:STM32L152VDTxX +MCU_ST_STM32L1:STM32L152VETx +MCU_ST_STM32L1:STM32L152VEYx +MCU_ST_STM32L1:STM32L152V_8-B_Hx +MCU_ST_STM32L1:STM32L152V_8-B_HxA +MCU_ST_STM32L1:STM32L152V_8-B_Tx +MCU_ST_STM32L1:STM32L152V_8-B_TxA +MCU_ST_STM32L1:STM32L152ZCTx +MCU_ST_STM32L1:STM32L152ZDTx +MCU_ST_STM32L1:STM32L152ZETx +MCU_ST_STM32L1:STM32L162QCHx +MCU_ST_STM32L1:STM32L162QDHx +MCU_ST_STM32L1:STM32L162RCTx +MCU_ST_STM32L1:STM32L162RCTxA +MCU_ST_STM32L1:STM32L162RDTx +MCU_ST_STM32L1:STM32L162RDYx +MCU_ST_STM32L1:STM32L162RETx +MCU_ST_STM32L1:STM32L162VCHx +MCU_ST_STM32L1:STM32L162VCTx +MCU_ST_STM32L1:STM32L162VCTxA +MCU_ST_STM32L1:STM32L162VDTx +MCU_ST_STM32L1:STM32L162VDYxX +MCU_ST_STM32L1:STM32L162VETx +MCU_ST_STM32L1:STM32L162VEYx +MCU_ST_STM32L1:STM32L162ZCTx +MCU_ST_STM32L1:STM32L162ZDTx +MCU_ST_STM32L1:STM32L162ZETx +MCU_ST_STM32L4:STM32L412C8Tx +MCU_ST_STM32L4:STM32L412C8Ux +MCU_ST_STM32L4:STM32L412CBTx +MCU_ST_STM32L4:STM32L412CBTxP +MCU_ST_STM32L4:STM32L412CBUx +MCU_ST_STM32L4:STM32L412CBUxP +MCU_ST_STM32L4:STM32L412K8Tx +MCU_ST_STM32L4:STM32L412K8Ux +MCU_ST_STM32L4:STM32L412KBTx +MCU_ST_STM32L4:STM32L412KBUx +MCU_ST_STM32L4:STM32L412R8Ix +MCU_ST_STM32L4:STM32L412R8Tx +MCU_ST_STM32L4:STM32L412RBIx +MCU_ST_STM32L4:STM32L412RBIxP +MCU_ST_STM32L4:STM32L412RBTx +MCU_ST_STM32L4:STM32L412RBTxP +MCU_ST_STM32L4:STM32L412T8Yx +MCU_ST_STM32L4:STM32L412TBYx +MCU_ST_STM32L4:STM32L412TBYxP +MCU_ST_STM32L4:STM32L422CBTx +MCU_ST_STM32L4:STM32L422CBUx +MCU_ST_STM32L4:STM32L422KBTx +MCU_ST_STM32L4:STM32L422KBUx +MCU_ST_STM32L4:STM32L422RBIx +MCU_ST_STM32L4:STM32L422RBTx +MCU_ST_STM32L4:STM32L422TBYx +MCU_ST_STM32L4:STM32L431CBTx +MCU_ST_STM32L4:STM32L431CBUx +MCU_ST_STM32L4:STM32L431CBYx +MCU_ST_STM32L4:STM32L431CCTx +MCU_ST_STM32L4:STM32L431CCUx +MCU_ST_STM32L4:STM32L431CCYx +MCU_ST_STM32L4:STM32L431C_B-C_Tx +MCU_ST_STM32L4:STM32L431C_B-C_Ux +MCU_ST_STM32L4:STM32L431C_B-C_Yx +MCU_ST_STM32L4:STM32L431KBUx +MCU_ST_STM32L4:STM32L431KCUx +MCU_ST_STM32L4:STM32L431K_B-C_Ux +MCU_ST_STM32L4:STM32L431RBIx +MCU_ST_STM32L4:STM32L431RBTx +MCU_ST_STM32L4:STM32L431RBYx +MCU_ST_STM32L4:STM32L431RCIx +MCU_ST_STM32L4:STM32L431RCTx +MCU_ST_STM32L4:STM32L431RCYx +MCU_ST_STM32L4:STM32L431R_B-C_Ix +MCU_ST_STM32L4:STM32L431R_B-C_Tx +MCU_ST_STM32L4:STM32L431R_B-C_Yx +MCU_ST_STM32L4:STM32L431VCIx +MCU_ST_STM32L4:STM32L431VCTx +MCU_ST_STM32L4:STM32L432KBUx +MCU_ST_STM32L4:STM32L432KCUx +MCU_ST_STM32L4:STM32L432K_B-C_Ux +MCU_ST_STM32L4:STM32L433CBTx +MCU_ST_STM32L4:STM32L433CBUx +MCU_ST_STM32L4:STM32L433CBYx +MCU_ST_STM32L4:STM32L433CCTx +MCU_ST_STM32L4:STM32L433CCUx +MCU_ST_STM32L4:STM32L433CCYx +MCU_ST_STM32L4:STM32L433C_B-C_Tx +MCU_ST_STM32L4:STM32L433C_B-C_Ux +MCU_ST_STM32L4:STM32L433C_B-C_Yx +MCU_ST_STM32L4:STM32L433RBIx +MCU_ST_STM32L4:STM32L433RBTx +MCU_ST_STM32L4:STM32L433RBYx +MCU_ST_STM32L4:STM32L433RCIx +MCU_ST_STM32L4:STM32L433RCTx +MCU_ST_STM32L4:STM32L433RCTxP +MCU_ST_STM32L4:STM32L433RCYx +MCU_ST_STM32L4:STM32L433R_B-C_Ix +MCU_ST_STM32L4:STM32L433R_B-C_Tx +MCU_ST_STM32L4:STM32L433R_B-C_Yx +MCU_ST_STM32L4:STM32L433VCIx +MCU_ST_STM32L4:STM32L433VCTx +MCU_ST_STM32L4:STM32L442KCUx +MCU_ST_STM32L4:STM32L443CCFx +MCU_ST_STM32L4:STM32L443CCTx +MCU_ST_STM32L4:STM32L443CCUx +MCU_ST_STM32L4:STM32L443CCYx +MCU_ST_STM32L4:STM32L443RCIx +MCU_ST_STM32L4:STM32L443RCTx +MCU_ST_STM32L4:STM32L443RCYx +MCU_ST_STM32L4:STM32L443VCIx +MCU_ST_STM32L4:STM32L443VCTx +MCU_ST_STM32L4:STM32L451CCUx +MCU_ST_STM32L4:STM32L451CETx +MCU_ST_STM32L4:STM32L451CEUx +MCU_ST_STM32L4:STM32L451C_C-E_Ux +MCU_ST_STM32L4:STM32L451RCIx +MCU_ST_STM32L4:STM32L451RCTx +MCU_ST_STM32L4:STM32L451RCYx +MCU_ST_STM32L4:STM32L451REIx +MCU_ST_STM32L4:STM32L451RETx +MCU_ST_STM32L4:STM32L451REYx +MCU_ST_STM32L4:STM32L451R_C-E_Ix +MCU_ST_STM32L4:STM32L451R_C-E_Tx +MCU_ST_STM32L4:STM32L451R_C-E_Yx +MCU_ST_STM32L4:STM32L451VCIx +MCU_ST_STM32L4:STM32L451VCTx +MCU_ST_STM32L4:STM32L451VEIx +MCU_ST_STM32L4:STM32L451VETx +MCU_ST_STM32L4:STM32L451V_C-E_Ix +MCU_ST_STM32L4:STM32L451V_C-E_Tx +MCU_ST_STM32L4:STM32L452CCUx +MCU_ST_STM32L4:STM32L452CETx +MCU_ST_STM32L4:STM32L452CETxP +MCU_ST_STM32L4:STM32L452CEUx +MCU_ST_STM32L4:STM32L452C_C-E_Ux +MCU_ST_STM32L4:STM32L452RCIx +MCU_ST_STM32L4:STM32L452RCTx +MCU_ST_STM32L4:STM32L452RCYx +MCU_ST_STM32L4:STM32L452REIx +MCU_ST_STM32L4:STM32L452RETx +MCU_ST_STM32L4:STM32L452RETxP +MCU_ST_STM32L4:STM32L452REYx +MCU_ST_STM32L4:STM32L452REYxP +MCU_ST_STM32L4:STM32L452R_C-E_Ix +MCU_ST_STM32L4:STM32L452R_C-E_Tx +MCU_ST_STM32L4:STM32L452R_C-E_Yx +MCU_ST_STM32L4:STM32L452VCIx +MCU_ST_STM32L4:STM32L452VCTx +MCU_ST_STM32L4:STM32L452VEIx +MCU_ST_STM32L4:STM32L452VETx +MCU_ST_STM32L4:STM32L452V_C-E_Ix +MCU_ST_STM32L4:STM32L452V_C-E_Tx +MCU_ST_STM32L4:STM32L462CETx +MCU_ST_STM32L4:STM32L462CEUx +MCU_ST_STM32L4:STM32L462REIx +MCU_ST_STM32L4:STM32L462RETx +MCU_ST_STM32L4:STM32L462REYx +MCU_ST_STM32L4:STM32L462VEIx +MCU_ST_STM32L4:STM32L462VETx +MCU_ST_STM32L4:STM32L471QEIx +MCU_ST_STM32L4:STM32L471QGIx +MCU_ST_STM32L4:STM32L471Q_E-G_Ix +MCU_ST_STM32L4:STM32L471RETx +MCU_ST_STM32L4:STM32L471RGTx +MCU_ST_STM32L4:STM32L471R_E-G_Tx +MCU_ST_STM32L4:STM32L471VETx +MCU_ST_STM32L4:STM32L471VGTx +MCU_ST_STM32L4:STM32L471V_E-G_Tx +MCU_ST_STM32L4:STM32L471ZEJx +MCU_ST_STM32L4:STM32L471ZETx +MCU_ST_STM32L4:STM32L471ZGJx +MCU_ST_STM32L4:STM32L471ZGTx +MCU_ST_STM32L4:STM32L471Z_E-G_Jx +MCU_ST_STM32L4:STM32L471Z_E-G_Tx +MCU_ST_STM32L4:STM32L475RCTx +MCU_ST_STM32L4:STM32L475RETx +MCU_ST_STM32L4:STM32L475RGTx +MCU_ST_STM32L4:STM32L475R_C-E-G_Tx +MCU_ST_STM32L4:STM32L475VCTx +MCU_ST_STM32L4:STM32L475VETx +MCU_ST_STM32L4:STM32L475VGTx +MCU_ST_STM32L4:STM32L475V_C-E-G_Tx +MCU_ST_STM32L4:STM32L476JEYx +MCU_ST_STM32L4:STM32L476JGYx +MCU_ST_STM32L4:STM32L476JGYxP +MCU_ST_STM32L4:STM32L476J_E-G_Yx +MCU_ST_STM32L4:STM32L476MEYx +MCU_ST_STM32L4:STM32L476MGYx +MCU_ST_STM32L4:STM32L476M_E-G_Yx +MCU_ST_STM32L4:STM32L476QEIx +MCU_ST_STM32L4:STM32L476QGIx +MCU_ST_STM32L4:STM32L476QGIxP +MCU_ST_STM32L4:STM32L476Q_E-G_Ix +MCU_ST_STM32L4:STM32L476RCTx +MCU_ST_STM32L4:STM32L476RETx +MCU_ST_STM32L4:STM32L476RGTx +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 +MCU_ST_STM32L4:STM32L476ZGTx +MCU_ST_STM32L4:STM32L476ZGTxP +MCU_ST_STM32L4:STM32L476Z_E-G_Tx +MCU_ST_STM32L4:STM32L486JGYx +MCU_ST_STM32L4:STM32L486QGIx +MCU_ST_STM32L4:STM32L486RGTx +MCU_ST_STM32L4:STM32L486VGTx +MCU_ST_STM32L4:STM32L486ZGTx +MCU_ST_STM32L4:STM32L496AEIx +MCU_ST_STM32L4:STM32L496AGIx +MCU_ST_STM32L4:STM32L496AGIxP +MCU_ST_STM32L4:STM32L496A_E-G_Ix +MCU_ST_STM32L4:STM32L496QEIx +MCU_ST_STM32L4:STM32L496QGIx +MCU_ST_STM32L4:STM32L496QGIxP +MCU_ST_STM32L4:STM32L496QGIxS +MCU_ST_STM32L4:STM32L496Q_E-G_Ix +MCU_ST_STM32L4:STM32L496RETx +MCU_ST_STM32L4:STM32L496RGTx +MCU_ST_STM32L4:STM32L496RGTxP +MCU_ST_STM32L4:STM32L496R_E-G_Tx +MCU_ST_STM32L4:STM32L496VETx +MCU_ST_STM32L4:STM32L496VGTx +MCU_ST_STM32L4:STM32L496VGTxP +MCU_ST_STM32L4:STM32L496VGYx +MCU_ST_STM32L4:STM32L496VGYxP +MCU_ST_STM32L4:STM32L496V_E-G_Tx +MCU_ST_STM32L4:STM32L496WGYxP +MCU_ST_STM32L4:STM32L496ZETx +MCU_ST_STM32L4:STM32L496ZGTx +MCU_ST_STM32L4:STM32L496ZGTxP +MCU_ST_STM32L4:STM32L496Z_E-G_Tx +MCU_ST_STM32L4:STM32L4A6AGIx +MCU_ST_STM32L4:STM32L4A6AGIxP +MCU_ST_STM32L4:STM32L4A6QGIx +MCU_ST_STM32L4:STM32L4A6QGIxP +MCU_ST_STM32L4:STM32L4A6RGTx +MCU_ST_STM32L4:STM32L4A6RGTxP +MCU_ST_STM32L4:STM32L4A6VGTx +MCU_ST_STM32L4:STM32L4A6VGTxP +MCU_ST_STM32L4:STM32L4A6VGYx +MCU_ST_STM32L4:STM32L4A6VGYxP +MCU_ST_STM32L4:STM32L4A6ZGTx +MCU_ST_STM32L4:STM32L4A6ZGTxP +MCU_ST_STM32L4:STM32L4P5AEIx +MCU_ST_STM32L4:STM32L4P5AGIx +MCU_ST_STM32L4:STM32L4P5AGIxP +MCU_ST_STM32L4:STM32L4P5A_G-E_Ix +MCU_ST_STM32L4:STM32L4P5CETx +MCU_ST_STM32L4:STM32L4P5CEUx +MCU_ST_STM32L4:STM32L4P5CGTx +MCU_ST_STM32L4:STM32L4P5CGTxP +MCU_ST_STM32L4:STM32L4P5CGUx +MCU_ST_STM32L4:STM32L4P5CGUxP +MCU_ST_STM32L4:STM32L4P5C_G-E_Tx +MCU_ST_STM32L4:STM32L4P5C_G-E_Ux +MCU_ST_STM32L4:STM32L4P5QEIx +MCU_ST_STM32L4:STM32L4P5QGIx +MCU_ST_STM32L4:STM32L4P5QGIxP +MCU_ST_STM32L4:STM32L4P5QGIxS +MCU_ST_STM32L4:STM32L4P5Q_G-E_Ix +MCU_ST_STM32L4:STM32L4P5RETx +MCU_ST_STM32L4:STM32L4P5RGTx +MCU_ST_STM32L4:STM32L4P5RGTxP +MCU_ST_STM32L4:STM32L4P5R_G-E_Tx +MCU_ST_STM32L4:STM32L4P5VETx +MCU_ST_STM32L4:STM32L4P5VEYx +MCU_ST_STM32L4:STM32L4P5VGTx +MCU_ST_STM32L4:STM32L4P5VGTxP +MCU_ST_STM32L4:STM32L4P5VGYx +MCU_ST_STM32L4:STM32L4P5VGYxP +MCU_ST_STM32L4:STM32L4P5V_G-E_Tx +MCU_ST_STM32L4:STM32L4P5V_G-E_Yx +MCU_ST_STM32L4:STM32L4P5ZETx +MCU_ST_STM32L4:STM32L4P5ZGTx +MCU_ST_STM32L4:STM32L4P5ZGTxP +MCU_ST_STM32L4:STM32L4P5Z_G-E_Tx +MCU_ST_STM32L4:STM32L4Q5AGIx +MCU_ST_STM32L4:STM32L4Q5AGIxP +MCU_ST_STM32L4:STM32L4Q5CGTx +MCU_ST_STM32L4:STM32L4Q5CGTxP +MCU_ST_STM32L4:STM32L4Q5CGUx +MCU_ST_STM32L4:STM32L4Q5CGUxP +MCU_ST_STM32L4:STM32L4Q5QGIx +MCU_ST_STM32L4:STM32L4Q5QGIxP +MCU_ST_STM32L4:STM32L4Q5RGTx +MCU_ST_STM32L4:STM32L4Q5RGTxP +MCU_ST_STM32L4:STM32L4Q5VGTx +MCU_ST_STM32L4:STM32L4Q5VGTxP +MCU_ST_STM32L4:STM32L4Q5VGYx +MCU_ST_STM32L4:STM32L4Q5VGYxP +MCU_ST_STM32L4:STM32L4Q5ZGTx +MCU_ST_STM32L4:STM32L4Q5ZGTxP +MCU_ST_STM32L4:STM32L4R5AGIx +MCU_ST_STM32L4:STM32L4R5AIIx +MCU_ST_STM32L4:STM32L4R5AIIxP +MCU_ST_STM32L4:STM32L4R5A_G-I_Ix +MCU_ST_STM32L4:STM32L4R5QGIx +MCU_ST_STM32L4:STM32L4R5QGIxS +MCU_ST_STM32L4:STM32L4R5QIIx +MCU_ST_STM32L4:STM32L4R5QIIxP +MCU_ST_STM32L4:STM32L4R5Q_G-I_Ix +MCU_ST_STM32L4:STM32L4R5VGTx +MCU_ST_STM32L4:STM32L4R5VITx +MCU_ST_STM32L4:STM32L4R5V_G-I_Tx +MCU_ST_STM32L4:STM32L4R5ZGTx +MCU_ST_STM32L4:STM32L4R5ZGYx +MCU_ST_STM32L4:STM32L4R5ZITx +MCU_ST_STM32L4:STM32L4R5ZITxP +MCU_ST_STM32L4:STM32L4R5ZIYx +MCU_ST_STM32L4:STM32L4R5Z_G-I_Tx +MCU_ST_STM32L4:STM32L4R5Z_G-I_Yx +MCU_ST_STM32L4:STM32L4R7AIIx +MCU_ST_STM32L4:STM32L4R7VITx +MCU_ST_STM32L4:STM32L4R7ZITx +MCU_ST_STM32L4:STM32L4R9AGIx +MCU_ST_STM32L4:STM32L4R9AIIx +MCU_ST_STM32L4:STM32L4R9A_G-I_Ix +MCU_ST_STM32L4:STM32L4R9VGTx +MCU_ST_STM32L4:STM32L4R9VITx +MCU_ST_STM32L4:STM32L4R9V_G-I_Tx +MCU_ST_STM32L4:STM32L4R9ZGJx +MCU_ST_STM32L4:STM32L4R9ZGTx +MCU_ST_STM32L4:STM32L4R9ZGYx +MCU_ST_STM32L4:STM32L4R9ZIJx +MCU_ST_STM32L4:STM32L4R9ZITx +MCU_ST_STM32L4:STM32L4R9ZIYx +MCU_ST_STM32L4:STM32L4R9ZIYxP +MCU_ST_STM32L4:STM32L4R9Z_G-I_Jx +MCU_ST_STM32L4:STM32L4R9Z_G-I_Tx +MCU_ST_STM32L4:STM32L4R9Z_G-I_Yx +MCU_ST_STM32L4:STM32L4S5AIIx +MCU_ST_STM32L4:STM32L4S5QIIx +MCU_ST_STM32L4:STM32L4S5VITx +MCU_ST_STM32L4:STM32L4S5ZITx +MCU_ST_STM32L4:STM32L4S5ZIYx +MCU_ST_STM32L4:STM32L4S7AIIx +MCU_ST_STM32L4:STM32L4S7VITx +MCU_ST_STM32L4:STM32L4S7ZITx +MCU_ST_STM32L4:STM32L4S9AIIx +MCU_ST_STM32L4:STM32L4S9VITx +MCU_ST_STM32L4:STM32L4S9ZIJx +MCU_ST_STM32L4:STM32L4S9ZITx +MCU_ST_STM32L4:STM32L4S9ZIYx +MCU_ST_STM32L5:STM32L552CCTx +MCU_ST_STM32L5:STM32L552CCUx +MCU_ST_STM32L5:STM32L552CETx +MCU_ST_STM32L5:STM32L552CETxP +MCU_ST_STM32L5:STM32L552CEUx +MCU_ST_STM32L5:STM32L552CEUxP +MCU_ST_STM32L5:STM32L552C_C-E_Tx +MCU_ST_STM32L5:STM32L552C_C-E_Ux +MCU_ST_STM32L5:STM32L552MEYxP +MCU_ST_STM32L5:STM32L552MEYxQ +MCU_ST_STM32L5:STM32L552QCIxQ +MCU_ST_STM32L5:STM32L552QEIx +MCU_ST_STM32L5:STM32L552QEIxP +MCU_ST_STM32L5:STM32L552QEIxQ +MCU_ST_STM32L5:STM32L552Q_C-E_IxQ +MCU_ST_STM32L5:STM32L552RCTx +MCU_ST_STM32L5:STM32L552RETx +MCU_ST_STM32L5:STM32L552RETxP +MCU_ST_STM32L5:STM32L552RETxQ +MCU_ST_STM32L5:STM32L552R_C-E_Tx +MCU_ST_STM32L5:STM32L552VCTxQ +MCU_ST_STM32L5:STM32L552VETx +MCU_ST_STM32L5:STM32L552VETxQ +MCU_ST_STM32L5:STM32L552V_C-E_TxQ +MCU_ST_STM32L5:STM32L552ZCTxQ +MCU_ST_STM32L5:STM32L552ZETx +MCU_ST_STM32L5:STM32L552ZETxQ +MCU_ST_STM32L5:STM32L552Z_C-E_TxQ +MCU_ST_STM32L5:STM32L562CETx +MCU_ST_STM32L5:STM32L562CETxP +MCU_ST_STM32L5:STM32L562CEUx +MCU_ST_STM32L5:STM32L562CEUxP +MCU_ST_STM32L5:STM32L562MEYxP +MCU_ST_STM32L5:STM32L562MEYxQ +MCU_ST_STM32L5:STM32L562QEIx +MCU_ST_STM32L5:STM32L562QEIxP +MCU_ST_STM32L5:STM32L562QEIxQ +MCU_ST_STM32L5:STM32L562RETx +MCU_ST_STM32L5:STM32L562RETxP +MCU_ST_STM32L5:STM32L562RETxQ +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 +MCU_ST_STM32MP1:STM32MP151AADx +MCU_ST_STM32MP1:STM32MP151CAAx +MCU_ST_STM32MP1:STM32MP151CABx +MCU_ST_STM32MP1:STM32MP151CACx +MCU_ST_STM32MP1:STM32MP151CADx +MCU_ST_STM32MP1:STM32MP151DAAx +MCU_ST_STM32MP1:STM32MP151DABx +MCU_ST_STM32MP1:STM32MP151DACx +MCU_ST_STM32MP1:STM32MP151DADx +MCU_ST_STM32MP1:STM32MP151FAAx +MCU_ST_STM32MP1:STM32MP151FABx +MCU_ST_STM32MP1:STM32MP151FACx +MCU_ST_STM32MP1:STM32MP151FADx +MCU_ST_STM32MP1:STM32MP153AAAx +MCU_ST_STM32MP1:STM32MP153AABx +MCU_ST_STM32MP1:STM32MP153AACx +MCU_ST_STM32MP1:STM32MP153AADx +MCU_ST_STM32MP1:STM32MP153CAAx +MCU_ST_STM32MP1:STM32MP153CABx +MCU_ST_STM32MP1:STM32MP153CACx +MCU_ST_STM32MP1:STM32MP153CADx +MCU_ST_STM32MP1:STM32MP153DAAx +MCU_ST_STM32MP1:STM32MP153DABx +MCU_ST_STM32MP1:STM32MP153DACx +MCU_ST_STM32MP1:STM32MP153DADx +MCU_ST_STM32MP1:STM32MP153FAAx +MCU_ST_STM32MP1:STM32MP153FABx +MCU_ST_STM32MP1:STM32MP153FACx +MCU_ST_STM32MP1:STM32MP153FADx +MCU_ST_STM32MP1:STM32MP157AAAx +MCU_ST_STM32MP1:STM32MP157AABx +MCU_ST_STM32MP1:STM32MP157AACx +MCU_ST_STM32MP1:STM32MP157AADx +MCU_ST_STM32MP1:STM32MP157CAAx +MCU_ST_STM32MP1:STM32MP157CABx +MCU_ST_STM32MP1:STM32MP157CACx +MCU_ST_STM32MP1:STM32MP157CADx +MCU_ST_STM32MP1:STM32MP157DAAx +MCU_ST_STM32MP1:STM32MP157DABx +MCU_ST_STM32MP1:STM32MP157DACx +MCU_ST_STM32MP1:STM32MP157DADx +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 +MCU_ST_STM32U5:STM32U575AIIxQ +MCU_ST_STM32U5:STM32U575CGTx +MCU_ST_STM32U5:STM32U575CGTxQ +MCU_ST_STM32U5:STM32U575CGUx +MCU_ST_STM32U5:STM32U575CGUxQ +MCU_ST_STM32U5:STM32U575CITx +MCU_ST_STM32U5:STM32U575CITxQ +MCU_ST_STM32U5:STM32U575CIUx +MCU_ST_STM32U5:STM32U575CIUxQ +MCU_ST_STM32U5:STM32U575OGYxQ +MCU_ST_STM32U5:STM32U575OIYxQ +MCU_ST_STM32U5:STM32U575QGIx +MCU_ST_STM32U5:STM32U575QGIxQ +MCU_ST_STM32U5:STM32U575QIIx +MCU_ST_STM32U5:STM32U575QIIxQ +MCU_ST_STM32U5:STM32U575RGTx +MCU_ST_STM32U5:STM32U575RGTxQ +MCU_ST_STM32U5:STM32U575RITx +MCU_ST_STM32U5:STM32U575RITxQ +MCU_ST_STM32U5:STM32U575VGTx +MCU_ST_STM32U5:STM32U575VGTxQ +MCU_ST_STM32U5:STM32U575VITx +MCU_ST_STM32U5:STM32U575VITxQ +MCU_ST_STM32U5:STM32U575ZGTx +MCU_ST_STM32U5:STM32U575ZGTxQ +MCU_ST_STM32U5:STM32U575ZITx +MCU_ST_STM32U5:STM32U575ZITxQ +MCU_ST_STM32U5:STM32U585AIIx +MCU_ST_STM32U5:STM32U585AIIxQ +MCU_ST_STM32U5:STM32U585CITx +MCU_ST_STM32U5:STM32U585CITxQ +MCU_ST_STM32U5:STM32U585CIUx +MCU_ST_STM32U5:STM32U585CIUxQ +MCU_ST_STM32U5:STM32U585OIYxQ +MCU_ST_STM32U5:STM32U585QIIx +MCU_ST_STM32U5:STM32U585QIIxQ +MCU_ST_STM32U5:STM32U585RITx +MCU_ST_STM32U5:STM32U585RITxQ +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 +MCU_ST_STM32WB:STM32WB15CCYx +MCU_ST_STM32WB:STM32WB30CEUxA +MCU_ST_STM32WB:STM32WB35CCUxA +MCU_ST_STM32WB:STM32WB35CEUxA +MCU_ST_STM32WB:STM32WB35C_C-E_UxA +MCU_ST_STM32WB:STM32WB50CGUx +MCU_ST_STM32WB:STM32WB55CCUx +MCU_ST_STM32WB:STM32WB55CEUx +MCU_ST_STM32WB:STM32WB55CGUx +MCU_ST_STM32WB:STM32WB55RCVx +MCU_ST_STM32WB:STM32WB55REVx +MCU_ST_STM32WB:STM32WB55RGVx +MCU_ST_STM32WB:STM32WB55VCQx +MCU_ST_STM32WB:STM32WB55VCYx +MCU_ST_STM32WB:STM32WB55VEQx +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 +MCU_ST_STM32WL:STM32WL55JCIx +MCU_ST_STM32WL:STM32WLE4C8Ux +MCU_ST_STM32WL:STM32WLE4CBUx +MCU_ST_STM32WL:STM32WLE4CCUx +MCU_ST_STM32WL:STM32WLE4J8Ix +MCU_ST_STM32WL:STM32WLE4JBIx +MCU_ST_STM32WL:STM32WLE4JCIx +MCU_ST_STM32WL:STM32WLE5C8Ux +MCU_ST_STM32WL:STM32WLE5CBUx +MCU_ST_STM32WL:STM32WLE5CCUx +MCU_ST_STM32WL:STM32WLE5J8Ix +MCU_ST_STM32WL:STM32WLE5JBIx +MCU_ST_STM32WL:STM32WLE5JCIx +MCU_ST_STM8:STM8AF6223 +MCU_ST_STM8:STM8AF6223A +MCU_ST_STM8:STM8AL3188T +MCU_ST_STM8:STM8AL3189T +MCU_ST_STM8:STM8AL318AT +MCU_ST_STM8:STM8AL3L88T +MCU_ST_STM8:STM8AL3L89T +MCU_ST_STM8:STM8AL3L8AT +MCU_ST_STM8:STM8L051F3P +MCU_ST_STM8:STM8L101F1U +MCU_ST_STM8:STM8L101F2P +MCU_ST_STM8:STM8L101F2U +MCU_ST_STM8:STM8L101F3P +MCU_ST_STM8:STM8L101F3U +MCU_ST_STM8:STM8L151C2T +MCU_ST_STM8:STM8L151C3T +MCU_ST_STM8:STM8L152R6T +MCU_ST_STM8:STM8L152R8T +MCU_ST_STM8:STM8S001J3M +MCU_ST_STM8:STM8S003F3P +MCU_ST_STM8:STM8S003F3U +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 +MCU_Texas:LM3S6911-EQC50 +MCU_Texas:LM3S6911-IQC50 +MCU_Texas:LM4F110B2QR +MCU_Texas:LM4F110C4QR +MCU_Texas:LM4F110E5QR +MCU_Texas:LM4F110H5QR +MCU_Texas:LM4F111B2QR +MCU_Texas:LM4F111C4QR +MCU_Texas:LM4F111E5QR +MCU_Texas:LM4F111H5QR +MCU_Texas:MSP432E401Y +MCU_Texas:TM4C1230C3PM +MCU_Texas:TM4C1230D5PM +MCU_Texas:TM4C1230E6PM +MCU_Texas:TM4C1230H6PM +MCU_Texas:TM4C1231C3PM +MCU_Texas:TM4C1231D5PM +MCU_Texas:TM4C1231E6PM +MCU_Texas:TM4C1231H6PM +MCU_Texas:TMS320LF2406 +MCU_Texas:TMS470R1B768 +MCU_Texas_MSP430:CC430F5133xRGZ +MCU_Texas_MSP430:CC430F5135xRGZ +MCU_Texas_MSP430:CC430F5137xRGZ +MCU_Texas_MSP430:MSP430AFE221IPW +MCU_Texas_MSP430:MSP430AFE222IPW +MCU_Texas_MSP430:MSP430AFE223IPW +MCU_Texas_MSP430:MSP430AFE231IPW +MCU_Texas_MSP430:MSP430AFE232IPW +MCU_Texas_MSP430:MSP430AFE233IPW +MCU_Texas_MSP430:MSP430AFE251IPW +MCU_Texas_MSP430:MSP430AFE252IPW +MCU_Texas_MSP430:MSP430AFE253IPW +MCU_Texas_MSP430:MSP430F1101AIDGV +MCU_Texas_MSP430:MSP430F1101AIDW +MCU_Texas_MSP430:MSP430F1101AIPW +MCU_Texas_MSP430:MSP430F1101AIRGE +MCU_Texas_MSP430:MSP430F1111AIDGV +MCU_Texas_MSP430:MSP430F1111AIDW +MCU_Texas_MSP430:MSP430F1111AIPW +MCU_Texas_MSP430:MSP430F1111AIRGE +MCU_Texas_MSP430:MSP430F1121AIDGV +MCU_Texas_MSP430:MSP430F1121AIDW +MCU_Texas_MSP430:MSP430F1121AIPW +MCU_Texas_MSP430:MSP430F1121AIRGE +MCU_Texas_MSP430:MSP430F1122IDW +MCU_Texas_MSP430:MSP430F1122IPW +MCU_Texas_MSP430:MSP430F1122IRHB +MCU_Texas_MSP430:MSP430F1132IDW +MCU_Texas_MSP430:MSP430F1132IPW +MCU_Texas_MSP430:MSP430F1132IRHB +MCU_Texas_MSP430:MSP430F1222IDW +MCU_Texas_MSP430:MSP430F1222IPW +MCU_Texas_MSP430:MSP430F1222IRHB +MCU_Texas_MSP430:MSP430F122IDW +MCU_Texas_MSP430:MSP430F122IPW +MCU_Texas_MSP430:MSP430F122IRHB +MCU_Texas_MSP430:MSP430F1232IDW +MCU_Texas_MSP430:MSP430F1232IPW +MCU_Texas_MSP430:MSP430F1232IRHB +MCU_Texas_MSP430:MSP430F123IDW +MCU_Texas_MSP430:MSP430F123IPW +MCU_Texas_MSP430:MSP430F123IRHB +MCU_Texas_MSP430:MSP430F2001IN +MCU_Texas_MSP430:MSP430F2001IPW +MCU_Texas_MSP430:MSP430F2001IRSA +MCU_Texas_MSP430:MSP430F2002IN +MCU_Texas_MSP430:MSP430F2002IPW +MCU_Texas_MSP430:MSP430F2002IRSA +MCU_Texas_MSP430:MSP430F2003IN +MCU_Texas_MSP430:MSP430F2003IPW +MCU_Texas_MSP430:MSP430F2003IRSA +MCU_Texas_MSP430:MSP430F2011IN +MCU_Texas_MSP430:MSP430F2011IPW +MCU_Texas_MSP430:MSP430F2011IRSA +MCU_Texas_MSP430:MSP430F2012IN +MCU_Texas_MSP430:MSP430F2012IPW +MCU_Texas_MSP430:MSP430F2012IRSA +MCU_Texas_MSP430:MSP430F2013IN +MCU_Texas_MSP430:MSP430F2013IPW +MCU_Texas_MSP430:MSP430F2013IRSA +MCU_Texas_MSP430:MSP430F2101IDGV +MCU_Texas_MSP430:MSP430F2101IDW +MCU_Texas_MSP430:MSP430F2101IPW +MCU_Texas_MSP430:MSP430F2101IRGE +MCU_Texas_MSP430:MSP430F2111IDGV +MCU_Texas_MSP430:MSP430F2111IDW +MCU_Texas_MSP430:MSP430F2111IPW +MCU_Texas_MSP430:MSP430F2111IRGE +MCU_Texas_MSP430:MSP430F2112IPW +MCU_Texas_MSP430:MSP430F2112IRHB +MCU_Texas_MSP430:MSP430F2112IRTV +MCU_Texas_MSP430:MSP430F2121IDGV +MCU_Texas_MSP430:MSP430F2121IDW +MCU_Texas_MSP430:MSP430F2121IPW +MCU_Texas_MSP430:MSP430F2121IRGE +MCU_Texas_MSP430:MSP430F2122IPW +MCU_Texas_MSP430:MSP430F2122IRHB +MCU_Texas_MSP430:MSP430F2122IRTV +MCU_Texas_MSP430:MSP430F2131IDGV +MCU_Texas_MSP430:MSP430F2131IDW +MCU_Texas_MSP430:MSP430F2131IPW +MCU_Texas_MSP430:MSP430F2131IRGE +MCU_Texas_MSP430:MSP430F2132IPW +MCU_Texas_MSP430:MSP430F2132IRHB +MCU_Texas_MSP430:MSP430F2132IRTV +MCU_Texas_MSP430:MSP430F2232IDA +MCU_Texas_MSP430:MSP430F2232IRHA +MCU_Texas_MSP430:MSP430F2232IYFF +MCU_Texas_MSP430:MSP430F2234IDA +MCU_Texas_MSP430:MSP430F2234IRHA +MCU_Texas_MSP430:MSP430F2234IYFF +MCU_Texas_MSP430:MSP430F2252IDA +MCU_Texas_MSP430:MSP430F2252IRHA +MCU_Texas_MSP430:MSP430F2252IYFF +MCU_Texas_MSP430:MSP430F2254IDA +MCU_Texas_MSP430:MSP430F2254IRHA +MCU_Texas_MSP430:MSP430F2254IYFF +MCU_Texas_MSP430:MSP430F2272IDA +MCU_Texas_MSP430:MSP430F2272IRHA +MCU_Texas_MSP430:MSP430F2272IYFF +MCU_Texas_MSP430:MSP430F2274IDA +MCU_Texas_MSP430:MSP430F2274IRHA +MCU_Texas_MSP430:MSP430F2274IYFF +MCU_Texas_MSP430:MSP430F2330IRHA +MCU_Texas_MSP430:MSP430F2330IYFF +MCU_Texas_MSP430:MSP430F2350IRHA +MCU_Texas_MSP430:MSP430F2350IYFF +MCU_Texas_MSP430:MSP430F2370IRHA +MCU_Texas_MSP430:MSP430F2370IYFF +MCU_Texas_MSP430:MSP430F2618-EP +MCU_Texas_MSP430:MSP430F5217IRGC +MCU_Texas_MSP430:MSP430F5217IYFF +MCU_Texas_MSP430:MSP430F5219IRGC +MCU_Texas_MSP430:MSP430F5219IYFF +MCU_Texas_MSP430:MSP430F5227IRGC +MCU_Texas_MSP430:MSP430F5227IYFF +MCU_Texas_MSP430:MSP430F5229IRGC +MCU_Texas_MSP430:MSP430F5229IYFF +MCU_Texas_MSP430:MSP430F5232IRGZ +MCU_Texas_MSP430:MSP430F5234IRGZ +MCU_Texas_MSP430:MSP430F5237IRGC +MCU_Texas_MSP430:MSP430F5239IRGC +MCU_Texas_MSP430:MSP430F5242IRGZ +MCU_Texas_MSP430:MSP430F5244IRGZ +MCU_Texas_MSP430:MSP430F5247IRGC +MCU_Texas_MSP430:MSP430F5249IRGC +MCU_Texas_MSP430:MSP430F5304IPT +MCU_Texas_MSP430:MSP430F5304IRGZ +MCU_Texas_MSP430:MSP430F5308IPT +MCU_Texas_MSP430:MSP430F5308IRGC +MCU_Texas_MSP430:MSP430F5308IRGZ +MCU_Texas_MSP430:MSP430F5308IZQE +MCU_Texas_MSP430:MSP430F5309IPT +MCU_Texas_MSP430:MSP430F5309IRGC +MCU_Texas_MSP430:MSP430F5309IRGZ +MCU_Texas_MSP430:MSP430F5309IZQE +MCU_Texas_MSP430:MSP430F5310IPT +MCU_Texas_MSP430:MSP430F5310IRGC +MCU_Texas_MSP430:MSP430F5310IRGZ +MCU_Texas_MSP430:MSP430F5310IZQE +MCU_Texas_MSP430:MSP430F5333IPZ +MCU_Texas_MSP430:MSP430F5333IZQW +MCU_Texas_MSP430:MSP430F5335IPZ +MCU_Texas_MSP430:MSP430F5335IZQW +MCU_Texas_MSP430:MSP430F5336IPZ +MCU_Texas_MSP430:MSP430F5336IZQW +MCU_Texas_MSP430:MSP430F5338IPZ +MCU_Texas_MSP430:MSP430F5338IZQW +MCU_Texas_MSP430:MSP430F5340IRGZ +MCU_Texas_MSP430:MSP430F5341IRGZ +MCU_Texas_MSP430:MSP430F5342IRGZ +MCU_Texas_MSP430:MSP430F5358IZQW +MCU_Texas_MSP430:MSP430F5359IZQW +MCU_Texas_MSP430:MSP430F5500IRGZ +MCU_Texas_MSP430:MSP430F5501IRGZ +MCU_Texas_MSP430:MSP430F5502IRGZ +MCU_Texas_MSP430:MSP430F5503IRGZ +MCU_Texas_MSP430:MSP430F5504IRGZ +MCU_Texas_MSP430:MSP430F5505IRGZ +MCU_Texas_MSP430:MSP430F5506IRGZ +MCU_Texas_MSP430:MSP430F5507IRGZ +MCU_Texas_MSP430:MSP430F5508IRGZ +MCU_Texas_MSP430:MSP430F5509IRGZ +MCU_Texas_MSP430:MSP430F5510IRGZ +MCU_Texas_MSP430:MSP430F5524IYFF +MCU_Texas_MSP430:MSP430F5526IYFF +MCU_Texas_MSP430:MSP430F5528IYFF +MCU_Texas_MSP430:MSP430F5630IZQW +MCU_Texas_MSP430:MSP430F5631IZQW +MCU_Texas_MSP430:MSP430F5632IZQW +MCU_Texas_MSP430:MSP430F5633IZQW +MCU_Texas_MSP430:MSP430F5634IZQW +MCU_Texas_MSP430:MSP430F5635IZQW +MCU_Texas_MSP430:MSP430F5636IZQW +MCU_Texas_MSP430:MSP430F5637IZQW +MCU_Texas_MSP430:MSP430F5638IZQW +MCU_Texas_MSP430:MSP430F5658IZQW +MCU_Texas_MSP430:MSP430F5659IZQW +MCU_Texas_MSP430:MSP430FR5720IRGE +MCU_Texas_MSP430:MSP430FR5722IRGE +MCU_Texas_MSP430:MSP430FR5724IRGE +MCU_Texas_MSP430:MSP430FR5726IRGE +MCU_Texas_MSP430:MSP430FR5728IRGE +MCU_Texas_MSP430:MSP430FR5730IRGE +MCU_Texas_MSP430:MSP430FR5732IRGE +MCU_Texas_MSP430:MSP430FR5734IRGE +MCU_Texas_MSP430:MSP430FR5736IRGE +MCU_Texas_MSP430:MSP430FR5738IRGE +MCU_Texas_MSP430:MSP430G2001IN14 +MCU_Texas_MSP430:MSP430G2001IPW14 +MCU_Texas_MSP430:MSP430G2001IRSA16 +MCU_Texas_MSP430:MSP430G2101IN14 +MCU_Texas_MSP430:MSP430G2101IPW14 +MCU_Texas_MSP430:MSP430G2101IRSA16 +MCU_Texas_MSP430:MSP430G2102IN20 +MCU_Texas_MSP430:MSP430G2102IPW14 +MCU_Texas_MSP430:MSP430G2102IPW20 +MCU_Texas_MSP430:MSP430G2102IRSA16 +MCU_Texas_MSP430:MSP430G2111IN14 +MCU_Texas_MSP430:MSP430G2111IPW14 +MCU_Texas_MSP430:MSP430G2111IRSA16 +MCU_Texas_MSP430:MSP430G2112IN20 +MCU_Texas_MSP430:MSP430G2112IPW14 +MCU_Texas_MSP430:MSP430G2112IPW20 +MCU_Texas_MSP430:MSP430G2112IRSA16 +MCU_Texas_MSP430:MSP430G2113IPW20 +MCU_Texas_MSP430:MSP430G2121IN14 +MCU_Texas_MSP430:MSP430G2121IPW14 +MCU_Texas_MSP430:MSP430G2121IRSA16 +MCU_Texas_MSP430:MSP430G2131IN14 +MCU_Texas_MSP430:MSP430G2131IPW14 +MCU_Texas_MSP430:MSP430G2131IRSA16 +MCU_Texas_MSP430:MSP430G2132IN20 +MCU_Texas_MSP430:MSP430G2132IPW14 +MCU_Texas_MSP430:MSP430G2132IPW20 +MCU_Texas_MSP430:MSP430G2132IRSA16 +MCU_Texas_MSP430:MSP430G2152IN20 +MCU_Texas_MSP430:MSP430G2152IPW14 +MCU_Texas_MSP430:MSP430G2152IPW20 +MCU_Texas_MSP430:MSP430G2152IRSA16 +MCU_Texas_MSP430:MSP430G2153IN20 +MCU_Texas_MSP430:MSP430G2153IPW20 +MCU_Texas_MSP430:MSP430G2153IPW28 +MCU_Texas_MSP430:MSP430G2153IRHB32 +MCU_Texas_MSP430:MSP430G2201IN14 +MCU_Texas_MSP430:MSP430G2201IPW14 +MCU_Texas_MSP430:MSP430G2201IRSA16 +MCU_Texas_MSP430:MSP430G2202IN20 +MCU_Texas_MSP430:MSP430G2202IPW14 +MCU_Texas_MSP430:MSP430G2202IPW20 +MCU_Texas_MSP430:MSP430G2202IRSA16 +MCU_Texas_MSP430:MSP430G2203IN20 +MCU_Texas_MSP430:MSP430G2203IPW20 +MCU_Texas_MSP430:MSP430G2203IPW28 +MCU_Texas_MSP430:MSP430G2203IRHB32 +MCU_Texas_MSP430:MSP430G2210ID +MCU_Texas_MSP430:MSP430G2211IN14 +MCU_Texas_MSP430:MSP430G2211IPW14 +MCU_Texas_MSP430:MSP430G2211IRSA16 +MCU_Texas_MSP430:MSP430G2212IN20 +MCU_Texas_MSP430:MSP430G2212IPW14 +MCU_Texas_MSP430:MSP430G2212IPW20 +MCU_Texas_MSP430:MSP430G2212IRSA16 +MCU_Texas_MSP430:MSP430G2213IN20 +MCU_Texas_MSP430:MSP430G2213IPW20 +MCU_Texas_MSP430:MSP430G2213IPW28 +MCU_Texas_MSP430:MSP430G2213IRHB32 +MCU_Texas_MSP430:MSP430G2221IN14 +MCU_Texas_MSP430:MSP430G2221IPW14 +MCU_Texas_MSP430:MSP430G2221IRSA16 +MCU_Texas_MSP430:MSP430G2230ID +MCU_Texas_MSP430:MSP430G2231IN14 +MCU_Texas_MSP430:MSP430G2231IPW14 +MCU_Texas_MSP430:MSP430G2231IRSA16 +MCU_Texas_MSP430:MSP430G2232IN20 +MCU_Texas_MSP430:MSP430G2232IPW14 +MCU_Texas_MSP430:MSP430G2232IPW20 +MCU_Texas_MSP430:MSP430G2232IRSA16 +MCU_Texas_MSP430:MSP430G2233IN20 +MCU_Texas_MSP430:MSP430G2233IPW20 +MCU_Texas_MSP430:MSP430G2233IPW28 +MCU_Texas_MSP430:MSP430G2233IRHB32 +MCU_Texas_MSP430:MSP430G2252IN20 +MCU_Texas_MSP430:MSP430G2252IPW14 +MCU_Texas_MSP430:MSP430G2252IPW20 +MCU_Texas_MSP430:MSP430G2252IRSA16 +MCU_Texas_MSP430:MSP430G2253IN20 +MCU_Texas_MSP430:MSP430G2253IPW20 +MCU_Texas_MSP430:MSP430G2253IPW28 +MCU_Texas_MSP430:MSP430G2253IRHB32 +MCU_Texas_MSP430:MSP430G2302IN20 +MCU_Texas_MSP430:MSP430G2302IPW14 +MCU_Texas_MSP430:MSP430G2302IPW20 +MCU_Texas_MSP430:MSP430G2302IRSA16 +MCU_Texas_MSP430:MSP430G2303IN20 +MCU_Texas_MSP430:MSP430G2303IPW20 +MCU_Texas_MSP430:MSP430G2303IPW28 +MCU_Texas_MSP430:MSP430G2303IRHB32 +MCU_Texas_MSP430:MSP430G2312IN20 +MCU_Texas_MSP430:MSP430G2312IPW14 +MCU_Texas_MSP430:MSP430G2312IPW20 +MCU_Texas_MSP430:MSP430G2312IRSA16 +MCU_Texas_MSP430:MSP430G2313IN20 +MCU_Texas_MSP430:MSP430G2313IPW20 +MCU_Texas_MSP430:MSP430G2313IPW28 +MCU_Texas_MSP430:MSP430G2313IRHB32 +MCU_Texas_MSP430:MSP430G2332IN20 +MCU_Texas_MSP430:MSP430G2332IPW14 +MCU_Texas_MSP430:MSP430G2332IPW20 +MCU_Texas_MSP430:MSP430G2332IRSA16 +MCU_Texas_MSP430:MSP430G2333IN20 +MCU_Texas_MSP430:MSP430G2333IPW20 +MCU_Texas_MSP430:MSP430G2333IPW28 +MCU_Texas_MSP430:MSP430G2333IRHB32 +MCU_Texas_MSP430:MSP430G2352IN20 +MCU_Texas_MSP430:MSP430G2352IPW14 +MCU_Texas_MSP430:MSP430G2352IPW20 +MCU_Texas_MSP430:MSP430G2352IRSA16 +MCU_Texas_MSP430:MSP430G2353IN20 +MCU_Texas_MSP430:MSP430G2353IPW20 +MCU_Texas_MSP430:MSP430G2353IPW28 +MCU_Texas_MSP430:MSP430G2353IRHB32 +MCU_Texas_MSP430:MSP430G2402IN20 +MCU_Texas_MSP430:MSP430G2402IPW14 +MCU_Texas_MSP430:MSP430G2402IPW20 +MCU_Texas_MSP430:MSP430G2402IRSA16 +MCU_Texas_MSP430:MSP430G2403IN20 +MCU_Texas_MSP430:MSP430G2403IPW20 +MCU_Texas_MSP430:MSP430G2403IPW28 +MCU_Texas_MSP430:MSP430G2403IRHB32 +MCU_Texas_MSP430:MSP430G2412IN20 +MCU_Texas_MSP430:MSP430G2412IPW14 +MCU_Texas_MSP430:MSP430G2412IPW20 +MCU_Texas_MSP430:MSP430G2412IRSA16 +MCU_Texas_MSP430:MSP430G2413IN20 +MCU_Texas_MSP430:MSP430G2413IPW20 +MCU_Texas_MSP430:MSP430G2413IPW28 +MCU_Texas_MSP430:MSP430G2413IRHB32 +MCU_Texas_MSP430:MSP430G2432IN20 +MCU_Texas_MSP430:MSP430G2432IPW14 +MCU_Texas_MSP430:MSP430G2432IPW20 +MCU_Texas_MSP430:MSP430G2432IRSA16 +MCU_Texas_MSP430:MSP430G2433IN20 +MCU_Texas_MSP430:MSP430G2433IPW20 +MCU_Texas_MSP430:MSP430G2433IPW28 +MCU_Texas_MSP430:MSP430G2433IRHB32 +MCU_Texas_MSP430:MSP430G2444IDA38 +MCU_Texas_MSP430:MSP430G2444IRHA40 +MCU_Texas_MSP430:MSP430G2444IYFF +MCU_Texas_MSP430:MSP430G2452IN20 +MCU_Texas_MSP430:MSP430G2452IPW14 +MCU_Texas_MSP430:MSP430G2452IPW20 +MCU_Texas_MSP430:MSP430G2452IRSA16 +MCU_Texas_MSP430:MSP430G2453IN20 +MCU_Texas_MSP430:MSP430G2453IPW20 +MCU_Texas_MSP430:MSP430G2453IPW28 +MCU_Texas_MSP430:MSP430G2453IRHB32 +MCU_Texas_MSP430:MSP430G2513IN20 +MCU_Texas_MSP430:MSP430G2513IPW20 +MCU_Texas_MSP430:MSP430G2513IPW28 +MCU_Texas_MSP430:MSP430G2513IRHB32 +MCU_Texas_MSP430:MSP430G2533IN20 +MCU_Texas_MSP430:MSP430G2533IPW20 +MCU_Texas_MSP430:MSP430G2533IPW28 +MCU_Texas_MSP430:MSP430G2533IRHB32 +MCU_Texas_MSP430:MSP430G2544IDA38 +MCU_Texas_MSP430:MSP430G2544IRHA40 +MCU_Texas_MSP430:MSP430G2544IYFF +MCU_Texas_MSP430:MSP430G2553IN20 +MCU_Texas_MSP430:MSP430G2553IPW20 +MCU_Texas_MSP430:MSP430G2553IPW28 +MCU_Texas_MSP430:MSP430G2553IRHB32 +MCU_Texas_MSP430:MSP430G2744IDA38 +MCU_Texas_MSP430:MSP430G2744IRHA40 +MCU_Texas_MSP430:MSP430G2744IYFF +MCU_Texas_MSP430:MSP430G2755IDA38 +MCU_Texas_MSP430:MSP430G2755IRHA40 +MCU_Texas_MSP430:MSP430G2855IDA38 +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 +Mechanical:Heatsink_Pad +Mechanical:Heatsink_Pad_2Pin +Mechanical:Heatsink_Pad_3Pin +Mechanical:Housing +Mechanical:Housing_Pad +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 +Memory_EEPROM:24LC04 +Memory_EEPROM:24LC08 +Memory_EEPROM:24LC1025 +Memory_EEPROM:24LC128 +Memory_EEPROM:24LC16 +Memory_EEPROM:24LC256 +Memory_EEPROM:24LC32 +Memory_EEPROM:24LC512 +Memory_EEPROM:24LC64 +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 +Memory_EEPROM:93AAxxB +Memory_EEPROM:93AAxxBT-xOT +Memory_EEPROM:93AAxxC +Memory_EEPROM:93CxxA +Memory_EEPROM:93CxxB +Memory_EEPROM:93CxxC +Memory_EEPROM:93LCxxA +Memory_EEPROM:93LCxxAxxOT +Memory_EEPROM:93LCxxB +Memory_EEPROM:93LCxxBxxOT +Memory_EEPROM:93LCxxC +Memory_EEPROM:AT24CS01-MAHM +Memory_EEPROM:AT24CS01-SSHM +Memory_EEPROM:AT24CS01-STUM +Memory_EEPROM:AT24CS01-XHM +Memory_EEPROM:AT24CS02-MAHM +Memory_EEPROM:AT24CS02-SSHM +Memory_EEPROM:AT24CS02-STUM +Memory_EEPROM:AT24CS02-XHM +Memory_EEPROM:AT24CS04-MAHM +Memory_EEPROM:AT24CS04-SSHM +Memory_EEPROM:AT24CS04-STUM +Memory_EEPROM:AT24CS04-XHM +Memory_EEPROM:AT24CS08-MAHM +Memory_EEPROM:AT24CS08-SSHM +Memory_EEPROM:AT24CS08-STUM +Memory_EEPROM:AT24CS08-XHM +Memory_EEPROM:AT24CS16-MAHM +Memory_EEPROM:AT24CS16-SSHM +Memory_EEPROM:AT24CS16-STUM +Memory_EEPROM:AT24CS16-XHM +Memory_EEPROM:AT24CS32-MAHM +Memory_EEPROM:AT24CS32-SSHM +Memory_EEPROM:AT24CS32-STUM +Memory_EEPROM:AT24CS32-XHM +Memory_EEPROM:AT24CS64-MAHM +Memory_EEPROM:AT24CS64-SSHM +Memory_EEPROM:AT24CS64-XHM +Memory_EEPROM:AT25xxx +Memory_EEPROM:AT25xxx-MA +Memory_EEPROM:BR25Sxxx +Memory_EEPROM:BR25xxx-NUX +Memory_EEPROM:CAT24C128 +Memory_EEPROM:CAT24C256 +Memory_EEPROM:CAT24M01L +Memory_EEPROM:CAT24M01W +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 +Memory_EEPROM:M24C01-RMN +Memory_EEPROM:M24C01-WDW +Memory_EEPROM:M24C01-WMN +Memory_EEPROM:M24C02-FDW +Memory_EEPROM:M24C02-FMN +Memory_EEPROM:M24C02-RDW +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 +Memory_EPROM:27512 +Memory_EPROM:2764 +Memory_EPROM:27C010 +Memory_EPROM:27C020 +Memory_EPROM:27C040 +Memory_EPROM:27C080 +Memory_EPROM:27C128 +Memory_EPROM:27C256 +Memory_EPROM:27C512 +Memory_EPROM:27C512PLCC +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 +Memory_Flash:AT25SF081-SSHF-X +Memory_Flash:AT25SF081-XMHD-X +Memory_Flash:AT25SF081-XMHF-X +Memory_Flash:AT25SL321-U +Memory_Flash:AT45DB161-JC +Memory_Flash:AT45DB161-RC +Memory_Flash:AT45DB161-TC +Memory_Flash:AT45DB161B-RC +Memory_Flash:AT45DB161B-RC-2.5 +Memory_Flash:AT45DB161B-TC +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 +Memory_Flash:M29W004 +Memory_Flash:M29W008 +Memory_Flash:MT25QUxxxxxx1xW7 +Memory_Flash:MX25L3233FM +Memory_Flash:MX25L3233FM1 +Memory_Flash:MX25L3233FM2 +Memory_Flash:MX25L3233FZN +Memory_Flash:MX25R3235FM1xx0 +Memory_Flash:MX25R3235FM1xx1 +Memory_Flash:MX25R3235FM2xx0 +Memory_Flash:MX25R3235FM2xx1 +Memory_Flash:MX25R3235FZNxx0 +Memory_Flash:MX25R3235FZNxx1 +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 +Memory_Flash:W25X20CLZP +Memory_Flash:W25X40CLSN +Memory_Flash:W25X40CLSS +Memory_Flash:W25X40CLSV +Memory_Flash:XTSD01G +Memory_Flash:XTSD02G +Memory_Flash:XTSD04G +Memory_Flash:XTSD08G +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 +Memory_NVRAM:FM18W08-SG +Memory_NVRAM:FM24C64B +Memory_NVRAM:FM24C64C +Memory_NVRAM:FM24CL16B +Memory_NVRAM:MB85RS128B +Memory_NVRAM:MB85RS16 +Memory_NVRAM:MB85RS1MT +Memory_NVRAM:MB85RS256B +Memory_NVRAM:MB85RS2MT +Memory_NVRAM:MB85RS512T +Memory_NVRAM:MB85RS64 +Memory_NVRAM:MR20H40 +Memory_NVRAM:MR25H40 +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 +Memory_RAM:IDT71V65903S +Memory_RAM:IDT7201 +Memory_RAM:IDT7202 +Memory_RAM:IDT7203 +Memory_RAM:IDT7204 +Memory_RAM:IDT7205 +Memory_RAM:IDT7206 +Memory_RAM:IDT7207 +Memory_RAM:IDT7208 +Memory_RAM:IS42S16400J-xC +Memory_RAM:IS42S16400J-xT +Memory_RAM:IS43LQ32256A-062BLI +Memory_RAM:IS43LQ32256AL-062BLI +Memory_RAM:IS61C5128AL-10KLI +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 +Memory_RAM:W9812G6KH-75 +Memory_ROM:XC18V01SO20 +Memory_ROM:XCF08P +Memory_UniqueID:DS2401P +Memory_UniqueID:DS2401Z +Motor:Fan +Motor:Fan_3pin +Motor:Fan_4pin +Motor:Fan_ALT +Motor:Fan_CPU_4pin +Motor:Fan_IEC-60617 +Motor:Fan_ISO-14617 +Motor:Fan_PC_Chassis +Motor:Fan_Tacho +Motor:Fan_Tacho_PWM +Motor:Motor_AC +Motor:Motor_DC +Motor:Motor_DC_ALT +Motor:Motor_Servo +Motor:Motor_Servo_AirTronics +Motor:Motor_Servo_Futaba_J +Motor:Motor_Servo_Grapner_JR +Motor:Motor_Servo_Hitec +Motor:Motor_Servo_JR +Motor:Motor_Servo_Robbe +Motor:Stepper_Motor_bipolar +Motor:Stepper_Motor_unipolar_5pin +Motor:Stepper_Motor_unipolar_6pin +Oscillator:5P49V6965 +Oscillator:ABLNO +Oscillator:ACO-xxxMHz +Oscillator:ACO-xxxMHz-A +Oscillator:ASCO +Oscillator:ASDMB-xxxMHz +Oscillator:ASE-xxxMHz +Oscillator:ASV-xxxMHz +Oscillator:CFPS-72 +Oscillator:CVCO55xx +Oscillator:CXO_DIP14 +Oscillator:CXO_DIP8 +Oscillator:DFA-S11 +Oscillator:DFA-S15 +Oscillator:DFA-S2 +Oscillator:DFA-S3 +Oscillator:DGOF5S3 +Oscillator:ECS-2520MV-xxx-xx +Oscillator:FT5HN +Oscillator:FT5HV +Oscillator:GTXO-14T +Oscillator:GTXO-14V +Oscillator:GTXO-S14T +Oscillator:GTXO-S14V +Oscillator:IQXO-70 +Oscillator:JTOS-25 +Oscillator:JTOS-50 +Oscillator:KC2520Z +Oscillator:KT2520K-T +Oscillator:LTC6905xS5-100 +Oscillator:LTC6905xS5-133 +Oscillator:LTC6905xS5-80 +Oscillator:LTC6905xS5-96 +Oscillator:MAX7375AXR105 +Oscillator:MAX7375AXR185 +Oscillator:MAX7375AXR365 +Oscillator:MAX7375AXR375 +Oscillator:MAX7375AXR405 +Oscillator:MAX7375AXR425 +Oscillator:MAX7375AXR805 +Oscillator:MV267 +Oscillator:MV317 +Oscillator:NB3N502 +Oscillator:NB3N511 +Oscillator:OCXO-14 +Oscillator:OH300 +Oscillator:SG-210SCD +Oscillator:SG-210SDD +Oscillator:SG-210SED +Oscillator:SG-210STF +Oscillator:SG-211 +Oscillator:SG-3030CM +Oscillator:SG-5032CAN +Oscillator:SG-5032CBN +Oscillator:SG-5032CCN +Oscillator:SG-51 +Oscillator:SG-531 +Oscillator:SG-615 +Oscillator:SG-7050CAN +Oscillator:SG-7050CBN +Oscillator:SG-7050CCN +Oscillator:SG-8002CA +Oscillator:SG-8002CE +Oscillator:SG-8002DB +Oscillator:SG-8002DC +Oscillator:SG-8002JA +Oscillator:SG-8002JC +Oscillator:SG-8002LB +Oscillator:Si512A_2.5x3.2mm +Oscillator:Si513A_2.5x3.2mm +Oscillator:Si5351A-B-GM +Oscillator:Si5351A-B-GT +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 +Oscillator:SiT9366xx-xBx-xxN +Oscillator:SiT9367xx-xBx-xxE +Oscillator:SiT9367xx-xBx-xxN +Oscillator:TCXO-14 +Oscillator:TCXO3 +Oscillator:TFT660 +Oscillator:TFT680 +Oscillator:TG2520SMN-xx.xxxxxxMhz-xxxxNM +Oscillator:TXC-7C +Oscillator:VC-81 +Oscillator:VC-83 +Oscillator:VTCXO-14 +Oscillator:XO32 +Oscillator:XO53 +Oscillator:XO91 +Oscillator:XUX51 +Oscillator:XUX52 +Oscillator:XUX53 +Oscillator:XUX71 +Oscillator:XUX72 +Oscillator:XUX73 +Oscillator:XUY51 +Oscillator:XUY52 +Oscillator:XUY53 +Oscillator:XUY71 +Oscillator:XUY72 +Oscillator:XUY73 +Potentiometer_Digital:AD5253 +Potentiometer_Digital:AD5254 +Potentiometer_Digital:AD5272BCP +Potentiometer_Digital:AD5272BRM +Potentiometer_Digital:AD5274BCP +Potentiometer_Digital:AD5274BRM +Potentiometer_Digital:AD5280 +Potentiometer_Digital:AD5282 +Potentiometer_Digital:AD5290 +Potentiometer_Digital:AD5293 +Potentiometer_Digital:DS1267_DIP +Potentiometer_Digital:DS1267_SOIC +Potentiometer_Digital:DS1267_TSSOP +Potentiometer_Digital:DS1882E +Potentiometer_Digital:MAX5436 +Potentiometer_Digital:MAX5438 +Potentiometer_Digital:MCP4011-xxxxMS +Potentiometer_Digital:MCP4011-xxxxSN +Potentiometer_Digital:MCP4012-xxxxCH +Potentiometer_Digital:MCP4013-xxxxCH +Potentiometer_Digital:MCP4014-xxxxOT +Potentiometer_Digital:MCP4017-xxxxLT +Potentiometer_Digital:MCP4018-xxxxLT +Potentiometer_Digital:MCP4019-xxxxLT +Potentiometer_Digital:MCP4021-xxxxMS +Potentiometer_Digital:MCP4021-xxxxSN +Potentiometer_Digital:MCP4022-xxxxCH +Potentiometer_Digital:MCP4023-xxxxCH +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 +Potentiometer_Digital:MCP4251-xxxx-ML +Potentiometer_Digital:MCP4251-xxxx-P +Potentiometer_Digital:MCP4251-xxxx-SL +Potentiometer_Digital:MCP4251-xxxx-ST +Potentiometer_Digital:MCP4431-xxxx-ST +Potentiometer_Digital:MCP4441-xxxx-ST +Potentiometer_Digital:MCP4451-xxxx-ST +Potentiometer_Digital:MCP4461-xxxx-ST +Potentiometer_Digital:MCP45HV31-MQ +Potentiometer_Digital:MCP45HV31-ST +Potentiometer_Digital:MCP45HV51-MQ +Potentiometer_Digital:MCP45HV51-ST +Potentiometer_Digital:TPL0401A-10-Q1 +Potentiometer_Digital:TPL0401B-10-Q1 +Potentiometer_Digital:X9118 +Potentiometer_Digital:X9250 +Potentiometer_Digital:X9258 +power:+10V +power:+12C +power:+12L +power:+12LF +power:+12P +power:+12V +power:+12VA +power:+15V +power:+1V0 +power:+1V1 +power:+1V2 +power:+1V35 +power:+1V5 +power:+1V8 +power:+24V +power:+28V +power:+2V5 +power:+2V8 +power:+3.3V +power:+3.3VA +power:+3.3VADC +power:+3.3VDAC +power:+3.3VP +power:+36V +power:+3V0 +power:+3V3 +power:+3V8 +power:+48V +power:+4V +power:+5C +power:+5F +power:+5P +power:+5V +power:+5VA +power:+5VD +power:+5VL +power:+5VP +power:+6V +power:+7.5V +power:+8V +power:+9V +power:+9VA +power:+BATT +power:+VDC +power:+VSW +power:-10V +power:-12V +power:-12VA +power:-15V +power:-24V +power:-2V5 +power:-36V +power:-3V3 +power:-48V +power:-5V +power:-5VA +power:-6V +power:-8V +power:-9V +power:-9VA +power:-BATT +power:-VDC +power:-VSW +power:AC +power:Earth +power:Earth_Clean +power:Earth_Protective +power:GND +power:GND1 +power:GND2 +power:GND3 +power:GNDA +power:GNDD +power:GNDPWR +power:GNDREF +power:GNDS +power:HT +power:LINE +power:NEUT +power:PRI_HI +power:PRI_LO +power:PRI_MID +power:PWR_FLAG +power:VAA +power:VAC +power:VBUS +power:VCC +power:VCCQ +power:VCOM +power:VD +power:VDC +power:VDD +power:VDDA +power:VDDF +power:VEE +power:VMEM +power:VPP +power:VS +power:VSS +power:VSSA +power:Vdrive +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 +Power_Management:AUIPS1051L +Power_Management:AUIPS1052G +Power_Management:AUIPS2031R +Power_Management:AUIPS2041L +Power_Management:AUIPS2051L +Power_Management:AUIPS2052G +Power_Management:AUIPS6011R +Power_Management:AUIPS6031R +Power_Management:AUIPS6041G +Power_Management:AUIPS6044G +Power_Management:AUIPS7081R +Power_Management:AUIPS7081S +Power_Management:AUIPS7091G +Power_Management:AUIPS7111S +Power_Management:AUIPS7121R +Power_Management:AUIPS7125R +Power_Management:AUIPS7141R +Power_Management:AUIPS7142G +Power_Management:AUIPS71451G +Power_Management:AUIPS7145R +Power_Management:AUIPS72211R +Power_Management:AUIPS7221R +Power_Management:AUIR3313S +Power_Management:AUIR3314S +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 +Power_Management:BD48xxFVE +Power_Management:BD49ExxG +Power_Management:BD49KxxG +Power_Management:BD49LxxG +Power_Management:BD49xxFVE +Power_Management:BQ24230RGT +Power_Management:BTN8982TA +Power_Management:BTS40K2-1EJC +Power_Management:BTS443P +Power_Management:BTS462TATMA1 +Power_Management:BTS50010-1TAD +Power_Management:BTS50055-1TMA +Power_Management:BTS50055-1TMC +Power_Management:BTS50080-1TEA +Power_Management:BTS50080-1TEB +Power_Management:BTS50080-1TMA +Power_Management:BTS50080-1TMC +Power_Management:BTS50085-1TMA +Power_Management:BTS5012SDA +Power_Management:BTS5014SDA +Power_Management:BTS5016SDA +Power_Management:BTS5030-1EJA +Power_Management:BTS5045-1EJA +Power_Management:BTS5090-1EJA +Power_Management:BTS5200-1EJA +Power_Management:BTS6133D +Power_Management:BTS6142D +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 +Power_Management:CAP004DG +Power_Management:CAP005DG +Power_Management:CAP006DG +Power_Management:CAP007DG +Power_Management:CAP008DG +Power_Management:CAP009DG +Power_Management:CAP012DG +Power_Management:CAP013DG +Power_Management:CAP014DG +Power_Management:CAP015DG +Power_Management:CAP016DG +Power_Management:CAP017DG +Power_Management:CAP018DG +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 +Power_Management:FPF2003 +Power_Management:FPF2004 +Power_Management:FPF2005 +Power_Management:FPF2006 +Power_Management:FPF2007 +Power_Management:HF81 +Power_Management:INA3221 +Power_Management:IPS6011PBF +Power_Management:IPS6011RPBF +Power_Management:IPS6011SPBF +Power_Management:IPS6021PBF +Power_Management:IPS6021RPBF +Power_Management:IPS6021SPBF +Power_Management:IPS6031PBF +Power_Management:IPS6031RPBF +Power_Management:IPS6031SPBF +Power_Management:IPS6041GPBF +Power_Management:IPS6041PBF +Power_Management:IPS6041RPBF +Power_Management:IPS6041SPBF +Power_Management:IPS7091GPBF +Power_Management:IPS7091PBF +Power_Management:IPS7091SPBF +Power_Management:IRS25751L +Power_Management:ITS5215 +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 +Power_Management:LT1641-1 +Power_Management:LT1641-2 +Power_Management:LT4230xDD +Power_Management:LT4231xUF +Power_Management:LT4320xDD-1 +Power_Management:LTC4242xUHF +Power_Management:LTC4357DCB +Power_Management:LTC4357MS8 +Power_Management:LTC4359-DCB +Power_Management:LTC4359-MS8 +Power_Management:LTC4364CDE +Power_Management:LTC4364CMS +Power_Management:LTC4364CS +Power_Management:LTC4364HDE +Power_Management:LTC4364HMS +Power_Management:LTC4364HS +Power_Management:LTC4364IDE +Power_Management:LTC4364IMS +Power_Management:LTC4364IS +Power_Management:LTC4365DDB +Power_Management:LTC4365DDB-1 +Power_Management:LTC4365TS8 +Power_Management:LTC4365TS8-1 +Power_Management:LTC4365xTS8 +Power_Management:LTC4370xDE +Power_Management:LTC4370xMS +Power_Management:LTC4412xS6 +Power_Management:LTC4417CGN +Power_Management:LTC4417CUF +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-1BN +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 +Power_Management:RT9701 +Power_Management:RT9742AGJ5F +Power_Management:RT9742ANGJ5F +Power_Management:RT9742BGJ5F +Power_Management:RT9742BNGJ5F +Power_Management:SN6505ADBV +Power_Management:SN6505BDBV +Power_Management:SN6507DGQ +Power_Management:STM6600 +Power_Management:STM6601 +Power_Management:SiP32431DR3 +Power_Management:SiP32432DR3 +Power_Management:TEA1708T +Power_Management:TLE8102SG +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 +Power_Management:TPS22993 +Power_Management:TPS2412D +Power_Management:TPS2412PW +Power_Management:TPS2419D +Power_Management:TPS2419PW +Power_Management:TPS2592xx +Power_Management:TPS26630RGE +Power_Management:TPS26631PWP +Power_Management:TPS26631RGE +Power_Management:TPS26632RGE +Power_Management:TPS26633PWP +Power_Management:TPS26633RGE +Power_Management:TPS26635RGE +Power_Management:TPS26636PWP +Power_Management:TSM102 +Power_Management:TSM102A +Power_Management:TSM103W +Power_Management:TSM103WA +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 +Power_Protection:NCP349MNBG +Power_Protection:NCP349MNBK +Power_Protection:NCP361MU +Power_Protection:NCP361SN +Power_Protection:NUF4401MN +Power_Protection:NUP2105L +Power_Protection:NUP2202 +Power_Protection:NUP4202 +Power_Protection:PCMF3USB3S +Power_Protection:PESD3V3L4UF +Power_Protection:PESD3V3L4UG +Power_Protection:PESD3V3L4UW +Power_Protection:PESD3V3L5UF +Power_Protection:PESD3V3L5UV +Power_Protection:PESD3V3L5UY +Power_Protection:PESD5V0L4UF +Power_Protection:PESD5V0L4UG +Power_Protection:PESD5V0L4UW +Power_Protection:PESD5V0L5UF +Power_Protection:PESD5V0L5UV +Power_Protection:PESD5V0L5UY +Power_Protection:PRTR5V0U2X +Power_Protection:RCLAMP0502B +Power_Protection:RCLAMP0502BA +Power_Protection:RCLAMP0582B +Power_Protection:RCLAMP3328P +Power_Protection:SN65220 +Power_Protection:SN65240 +Power_Protection:SN75240 +Power_Protection:SP0502BAHT +Power_Protection:SP0502BAJT +Power_Protection:SP0503BAHT +Power_Protection:SP0504BAHT +Power_Protection:SP0504BAJT +Power_Protection:SP0505BAHT +Power_Protection:SP0505BAJT +Power_Protection:SP7538P +Power_Protection:SRV05-4 +Power_Protection:SZNUP2105L +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 +Power_Protection:TVS1800DRV +Power_Protection:TVS2200DRV +Power_Protection:TVS2700DRV +Power_Protection:TVS3300DRV +Power_Protection:USB6B1 +Power_Protection:USBLC6-2P6 +Power_Protection:USBLC6-2SC6 +Power_Protection:USBLC6-4SC6 +Power_Protection:WE-TVS-82400102 +Power_Protection:WE-TVS-824014881 +Power_Protection:WE-TVS-824015043 +Power_Protection:ZEN056V075A48LS +Power_Protection:ZEN056V115A24LS +Power_Protection:ZEN056V130A24LS +Power_Protection:ZEN056V230A16LS +Power_Protection:ZEN059V130A24LS +Power_Protection:ZEN065V130A24LS +Power_Protection:ZEN065V230A16LS +Power_Protection:ZEN098V130A24LS +Power_Protection:ZEN098V230A16LS +Power_Protection:ZEN132V075A48LS +Power_Protection:ZEN132V130A24LS +Power_Protection:ZEN132V230A16LS +Power_Protection:ZEN164V130A24LS +Power_Supervisor:CAT811JTBI-GT3 +Power_Supervisor:CAT811LTBI-GT3 +Power_Supervisor:CAT811MTBI-GT3 +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 +Power_Supervisor:MAX16050xTI +Power_Supervisor:MAX16051xTI +Power_Supervisor:MAX6355 +Power_Supervisor:MAX6369 +Power_Supervisor:MAX6370 +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 +Power_Supervisor:MAX811SEUS-T +Power_Supervisor:MAX811TEUS-T +Power_Supervisor:MC34064D +Power_Supervisor:MC34064DM +Power_Supervisor:MC34064P +Power_Supervisor:MC34064SN +Power_Supervisor:MCP100-270D +Power_Supervisor:MCP100-300D +Power_Supervisor:MCP100-315D +Power_Supervisor:MCP100-450D +Power_Supervisor:MCP100-460D +Power_Supervisor:MCP100-475D +Power_Supervisor:MCP100-485D +Power_Supervisor:MCP101-270D +Power_Supervisor:MCP101-300D +Power_Supervisor:MCP101-315D +Power_Supervisor:MCP101-450D +Power_Supervisor:MCP101-460D +Power_Supervisor:MCP101-475D +Power_Supervisor:MCP101-485D +Power_Supervisor:MCP120-xxxDxTO +Power_Supervisor:MCP120-xxxGxTO +Power_Supervisor:MCP120-xxxHxTO +Power_Supervisor:MCP120-xxxxSN +Power_Supervisor:MCP120-xxxxTT +Power_Supervisor:MCP130-xxxDxTO +Power_Supervisor:MCP130-xxxFxTO +Power_Supervisor:MCP130-xxxHxTO +Power_Supervisor:MCP130-xxxxSN +Power_Supervisor:MCP130-xxxxTT +Power_Supervisor:MIC811JUY +Power_Supervisor:MIC811LUY +Power_Supervisor:MIC811MUY +Power_Supervisor:MIC811RUY +Power_Supervisor:MIC811SUY +Power_Supervisor:MIC811TUY +Power_Supervisor:TCM809 +Power_Supervisor:TCM810 +Power_Supervisor:TL7702A +Power_Supervisor:TL7702B +Power_Supervisor:TL7705A +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 +Power_Supervisor:TPS3839DBZ +Power_Supervisor:TPS3839DQN +Reference_Current:LM134H +Reference_Current:LM234Z-3 +Reference_Current:LM234Z-6 +Reference_Current:LM334M +Reference_Current:LM334SM +Reference_Current:LM334Z +Reference_Current:LM334Z-LFT1 +Reference_Current:LT3092xDD +Reference_Current:LT3092xST +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 +Reference_Voltage:ADR4533 +Reference_Voltage:ADR4540 +Reference_Voltage:ADR4550 +Reference_Voltage:CJ432 +Reference_Voltage:ISL21070CIH320Z-TK +Reference_Voltage:ISL21070CIH325Z-TK +Reference_Voltage:ISL21070DIH306Z-TK +Reference_Voltage:LM285D-1.2 +Reference_Voltage:LM285D-2.5 +Reference_Voltage:LM285M-ADJ +Reference_Voltage:LM285S-1.2 +Reference_Voltage:LM285S-2.5 +Reference_Voltage:LM285Z-1.2 +Reference_Voltage:LM285Z-2.5 +Reference_Voltage:LM285Z-ADJ +Reference_Voltage:LM329xZ +Reference_Voltage:LM385BZ-1.2 +Reference_Voltage:LM385BZ-2.5 +Reference_Voltage:LM385D-1.2 +Reference_Voltage:LM385D-2.5 +Reference_Voltage:LM385M-ADJ +Reference_Voltage:LM385S-1.2 +Reference_Voltage:LM385S-2.5 +Reference_Voltage:LM385Z-1.2 +Reference_Voltage:LM385Z-2.5 +Reference_Voltage:LM385Z-ADJ +Reference_Voltage:LM399 +Reference_Voltage:LM4030-2.5 +Reference_Voltage:LM4030-4.096 +Reference_Voltage:LM4040DBZ-10 +Reference_Voltage:LM4040DBZ-2.0 +Reference_Voltage:LM4040DBZ-2.5 +Reference_Voltage:LM4040DBZ-3 +Reference_Voltage:LM4040DBZ-4.1 +Reference_Voltage:LM4040DBZ-5 +Reference_Voltage:LM4040DBZ-8.2 +Reference_Voltage:LM4040DCK-10 +Reference_Voltage:LM4040DCK-2.0 +Reference_Voltage:LM4040DCK-2.5 +Reference_Voltage:LM4040DCK-3 +Reference_Voltage:LM4040DCK-4.1 +Reference_Voltage:LM4040DCK-5 +Reference_Voltage:LM4040DCK-8.2 +Reference_Voltage:LM4040LP-10 +Reference_Voltage:LM4040LP-2.0 +Reference_Voltage:LM4040LP-2.5 +Reference_Voltage:LM4040LP-3 +Reference_Voltage:LM4040LP-4.1 +Reference_Voltage:LM4040LP-5 +Reference_Voltage:LM4040LP-8.2 +Reference_Voltage:LM4041LP-ADJ +Reference_Voltage:LM4125AIM5-2.5 +Reference_Voltage:LM4125IM5-2.0 +Reference_Voltage:LM4125IM5-2.5 +Reference_Voltage:LM4128 +Reference_Voltage:LM4132xMF-1.8 +Reference_Voltage:LM4132xMF-2.0 +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 +Reference_Voltage:LT1461AxS8-3.3 +Reference_Voltage:LT1461AxS8-4 +Reference_Voltage:LT1461AxS8-5 +Reference_Voltage:LT1461BxS8-2.5 +Reference_Voltage:LT1461BxS8-3 +Reference_Voltage:LT1461BxS8-3.3 +Reference_Voltage:LT1461BxS8-4 +Reference_Voltage:LT1461BxS8-5 +Reference_Voltage:LT1461CxS8-2.5 +Reference_Voltage:LT1461CxS8-3 +Reference_Voltage:LT1461CxS8-3.3 +Reference_Voltage:LT1461CxS8-4 +Reference_Voltage:LT1461CxS8-5 +Reference_Voltage:LT1461DxS8-2.5 +Reference_Voltage:LT1461DxS8-3 +Reference_Voltage:LT1461DxS8-3.3 +Reference_Voltage:LT1461DxS8-4 +Reference_Voltage:LT1461DxS8-5 +Reference_Voltage:LT1634BCMS8-1.25 +Reference_Voltage:LT1634BCMS8-2.5 +Reference_Voltage:LT1634CCZ-1.25 +Reference_Voltage:LT1634CCZ-2.5 +Reference_Voltage:LT1634CCZ-4.096 +Reference_Voltage:LT1634CCZ-5 +Reference_Voltage:LT1634xxS8-1.25 +Reference_Voltage:LT1634xxS8-2.5 +Reference_Voltage:LT1634xxS8-4.096 +Reference_Voltage:LT1634xxS8-5 +Reference_Voltage:LT1790-1.25 +Reference_Voltage:LT1790-2.048 +Reference_Voltage:LT1790-2.5 +Reference_Voltage:LT1790-3 +Reference_Voltage:LT1790-3.3 +Reference_Voltage:LT1790-4.096 +Reference_Voltage:LT1790-5 +Reference_Voltage:LT6657AHMS8-2.5 +Reference_Voltage:LT6657AHMS8-3 +Reference_Voltage:LT6657AHMS8-4.096 +Reference_Voltage:LT6657AHMS8-5 +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 +Reference_Voltage:MAX6103 +Reference_Voltage:MAX6104 +Reference_Voltage:MAX6105 +Reference_Voltage:MAX6106 +Reference_Voltage:MAX6107 +Reference_Voltage:MAX6225 +Reference_Voltage:MAX6241 +Reference_Voltage:MAX6250 +Reference_Voltage:MAX6325 +Reference_Voltage:MAX6341 +Reference_Voltage:MAX6350 +Reference_Voltage:MAX872 +Reference_Voltage:MAX874 +Reference_Voltage:MCP1501-10xCH +Reference_Voltage:MCP1501-10xRW +Reference_Voltage:MCP1501-10xSN +Reference_Voltage:MCP1501-12xCH +Reference_Voltage:MCP1501-12xRW +Reference_Voltage:MCP1501-12xSN +Reference_Voltage:MCP1501-18xCH +Reference_Voltage:MCP1501-18xRW +Reference_Voltage:MCP1501-18xSN +Reference_Voltage:MCP1501-20xCH +Reference_Voltage:MCP1501-20xRW +Reference_Voltage:MCP1501-20xSN +Reference_Voltage:MCP1501-25xCH +Reference_Voltage:MCP1501-25xRW +Reference_Voltage:MCP1501-25xSN +Reference_Voltage:MCP1501-30xCH +Reference_Voltage:MCP1501-30xRW +Reference_Voltage:MCP1501-30xSN +Reference_Voltage:MCP1501-33xCH +Reference_Voltage:MCP1501-33xRW +Reference_Voltage:MCP1501-33xSN +Reference_Voltage:MCP1501-40xCH +Reference_Voltage:MCP1501-40xRW +Reference_Voltage:MCP1501-40xSN +Reference_Voltage:MCP1525-TO +Reference_Voltage:MCP1525-TT +Reference_Voltage:MCP1541-TO +Reference_Voltage:MCP1541-TT +Reference_Voltage:REF01CP +Reference_Voltage:REF01CS +Reference_Voltage:REF01EZ +Reference_Voltage:REF01HP +Reference_Voltage:REF01HZ +Reference_Voltage:REF02AP +Reference_Voltage:REF02AU +Reference_Voltage:REF02AZ +Reference_Voltage:REF02BP +Reference_Voltage:REF02BU +Reference_Voltage:REF02CP +Reference_Voltage:REF02CS +Reference_Voltage:REF02EZ +Reference_Voltage:REF02HP +Reference_Voltage:REF02HS +Reference_Voltage:REF02HZ +Reference_Voltage:REF02Z +Reference_Voltage:REF03GP +Reference_Voltage:REF03GS +Reference_Voltage:REF102AP +Reference_Voltage:REF102AU +Reference_Voltage:REF102BP +Reference_Voltage:REF102BU +Reference_Voltage:REF102CP +Reference_Voltage:REF102CU +Reference_Voltage:REF191 +Reference_Voltage:REF192 +Reference_Voltage:REF193 +Reference_Voltage:REF194 +Reference_Voltage:REF195 +Reference_Voltage:REF196 +Reference_Voltage:REF198 +Reference_Voltage:REF2025 +Reference_Voltage:REF2030 +Reference_Voltage:REF2033 +Reference_Voltage:REF2041 +Reference_Voltage:REF3012 +Reference_Voltage:REF3020 +Reference_Voltage:REF3025 +Reference_Voltage:REF3030 +Reference_Voltage:REF3033 +Reference_Voltage:REF3040 +Reference_Voltage:REF3212AMDBVREP +Reference_Voltage:REF3220AMDBVREP +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 +Reference_Voltage:REF5010IDGK +Reference_Voltage:REF5020AD +Reference_Voltage:REF5020ADGK +Reference_Voltage:REF5020ID +Reference_Voltage:REF5020IDGK +Reference_Voltage:REF5025AD +Reference_Voltage:REF5025ADGK +Reference_Voltage:REF5025ID +Reference_Voltage:REF5025IDGK +Reference_Voltage:REF5030AD +Reference_Voltage:REF5030ADGK +Reference_Voltage:REF5030ID +Reference_Voltage:REF5030IDGK +Reference_Voltage:REF5040AD +Reference_Voltage:REF5040ADGK +Reference_Voltage:REF5040ID +Reference_Voltage:REF5040IDGK +Reference_Voltage:REF5045AD +Reference_Voltage:REF5045ADGK +Reference_Voltage:REF5045ID +Reference_Voltage:REF5045IDGK +Reference_Voltage:REF5050AD +Reference_Voltage:REF5050ADGK +Reference_Voltage:REF5050ID +Reference_Voltage:REF5050IDGK +Reference_Voltage:REF6025xDGK +Reference_Voltage:REF6030xDGK +Reference_Voltage:REF6033xDGK +Reference_Voltage:REF6041xDGK +Reference_Voltage:REF6045xDGK +Reference_Voltage:REF6050xDGK +Reference_Voltage:TL431D +Reference_Voltage:TL431DBV +Reference_Voltage:TL431DBZ +Reference_Voltage:TL431DCK +Reference_Voltage:TL431KTP +Reference_Voltage:TL431LP +Reference_Voltage:TL431P +Reference_Voltage:TL431PK +Reference_Voltage:TL431PS +Reference_Voltage:TL431PW +Reference_Voltage:TL432DBV +Reference_Voltage:TL432DBZ +Reference_Voltage:TL432PK +Reference_Voltage:TLE2425xD +Reference_Voltage:TLE2425xLP +Reference_Voltage:TLE2426xD +Reference_Voltage:TLE2426xLP +Reference_Voltage:TLE2426xP +Regulator_Controller:ICE1PCS01 +Regulator_Controller:ICE1PCS02 +Regulator_Controller:ICE2PCS01 +Regulator_Controller:ICE2PCS02 +Regulator_Controller:ICE2PCS03 +Regulator_Controller:ICE2PCS04 +Regulator_Controller:ICE2PCS05 +Regulator_Controller:ICE2PCS06 +Regulator_Controller:ICE3PCS01 +Regulator_Controller:ICE3PCS02 +Regulator_Controller:ICE3PCS03 +Regulator_Controller:IR1153S +Regulator_Controller:IR1155S +Regulator_Controller:IR1161L +Regulator_Controller:IR11662S +Regulator_Controller:IR11672AS +Regulator_Controller:IR1167S +Regulator_Controller:IR11682S +Regulator_Controller:IR11688S +Regulator_Controller:IR1168S +Regulator_Controller:IR1169S +Regulator_Controller:IRS2505L +Regulator_Controller:ISL6551 +Regulator_Controller:L4981A +Regulator_Controller:L4981B +Regulator_Controller:L4984D +Regulator_Controller:L6561 +Regulator_Controller:L6562 +Regulator_Controller:L6562A +Regulator_Controller:L6562AT +Regulator_Controller:L6563 +Regulator_Controller:L6563A +Regulator_Controller:L6563H +Regulator_Controller:L6563S +Regulator_Controller:L6564 +Regulator_Controller:L6564H +Regulator_Controller:L6564T +Regulator_Controller:L6598 +Regulator_Controller:L6599 +Regulator_Controller:L6727 +Regulator_Controller:LM25119 +Regulator_Controller:LM3478MA +Regulator_Controller:LM3478MM +Regulator_Controller:LM3478QMM +Regulator_Controller:LM5023 +Regulator_Controller:LT1248 +Regulator_Controller:LT1249 +Regulator_Controller:LT1509 +Regulator_Controller:LT4295xUFD +Regulator_Controller:LTC1624CS8 +Regulator_Controller:LTC3805xMSE +Regulator_Controller:LTC3890 +Regulator_Controller:LTC3890-1 +Regulator_Controller:LTC3892 +Regulator_Controller:LTC3892-1 +Regulator_Controller:LTC3892-2 +Regulator_Controller:LTC7810 +Regulator_Controller:NCP1200D100 +Regulator_Controller:NCP1200D40 +Regulator_Controller:NCP1200D60 +Regulator_Controller:NCP1200P100 +Regulator_Controller:NCP1200P40 +Regulator_Controller:NCP1200P60 +Regulator_Controller:NCP1203D100 +Regulator_Controller:NCP1203D40 +Regulator_Controller:NCP1203D60 +Regulator_Controller:NCP1203P100 +Regulator_Controller:NCP1203P40 +Regulator_Controller:NCP1203P60 +Regulator_Controller:NCP1207A +Regulator_Controller:NCP1207B +Regulator_Controller:NCP1217AD100 +Regulator_Controller:NCP1217AD133 +Regulator_Controller:NCP1217AD65 +Regulator_Controller:NCP1217AP100 +Regulator_Controller:NCP1217AP133 +Regulator_Controller:NCP1217AP65 +Regulator_Controller:NCP1217D100 +Regulator_Controller:NCP1217D133 +Regulator_Controller:NCP1217D65 +Regulator_Controller:NCP1217P100 +Regulator_Controller:NCP1217P133 +Regulator_Controller:NCP1217P65 +Regulator_Controller:NCP1280 +Regulator_Controller:NCP1380A +Regulator_Controller:NCP1380B +Regulator_Controller:NCP1380C +Regulator_Controller:NCP1380D +Regulator_Controller:NCP1653 +Regulator_Controller:NCP1653A +Regulator_Controller:NCP4308AD +Regulator_Controller:NCP4308AMT +Regulator_Controller:NCP4308DD +Regulator_Controller:NCP4308DMN +Regulator_Controller:NCP4308DMT +Regulator_Controller:NCP4308QD +Regulator_Controller:SG3525 +Regulator_Controller:SG3527 +Regulator_Controller:TDA4862 +Regulator_Controller:TDA4863 +Regulator_Controller:TDA4863-2 +Regulator_Controller:TL494 +Regulator_Controller:TPS2375-1 +Regulator_Controller:UC3525 +Regulator_Controller:UC3527 +Regulator_Controller:UC3842_DIP8 +Regulator_Controller:UC3842_SOIC14 +Regulator_Controller:UC3842_SOIC16 +Regulator_Controller:UC3842_SOIC8 +Regulator_Controller:UC3843_DIP8 +Regulator_Controller:UC3843_SOIC14 +Regulator_Controller:UC3843_SOIC8 +Regulator_Controller:UC3844_DIP8 +Regulator_Controller:UC3844_SOIC14 +Regulator_Controller:UC3844_SOIC8 +Regulator_Controller:UC3845_DIP8 +Regulator_Controller:UC3845_SOIC14 +Regulator_Controller:UC3845_SOIC8 +Regulator_Controller:UC3854 +Regulator_Controller:UCC1895J +Regulator_Controller:UCC24610D +Regulator_Controller:UCC24610DRB +Regulator_Controller:UCC24612-1DBV +Regulator_Controller:UCC24612-2DBV +Regulator_Controller:UCC24630DBV +Regulator_Controller:UCC24636DBV +Regulator_Controller:UCC25600 +Regulator_Controller:UCC256301 +Regulator_Controller:UCC25800 +Regulator_Controller:UCC2891 +Regulator_Controller:UCC2892 +Regulator_Controller:UCC2893 +Regulator_Controller:UCC2894 +Regulator_Controller:UCC2895DW +Regulator_Controller:UCC2895N +Regulator_Controller:UCC2895PW +Regulator_Controller:UCC2897 +Regulator_Controller:UCC3800 +Regulator_Controller:UCC3801 +Regulator_Controller:UCC3802 +Regulator_Controller:UCC3803 +Regulator_Controller:UCC3804 +Regulator_Controller:UCC3805 +Regulator_Controller:UCC3808D +Regulator_Controller:UCC3808N +Regulator_Controller:UCC3813-0 +Regulator_Controller:UCC3813-1 +Regulator_Controller:UCC3813-2 +Regulator_Controller:UCC3813-3 +Regulator_Controller:UCC3813-4 +Regulator_Controller:UCC3813-5 +Regulator_Controller:UCC3895DW +Regulator_Controller:UCC3895N +Regulator_Controller:UCC3895PW +Regulator_Current:HV100K5-G +Regulator_Current:HV101K5-G +Regulator_Linear:ADP3336ARMZ +Regulator_Linear:ADP7118ACPZN +Regulator_Linear:ADP7142ARDZ +Regulator_Linear:ADP7142ARDZ-1.8 +Regulator_Linear:ADP7142ARDZ-2.5 +Regulator_Linear:ADP7142ARDZ-3.3 +Regulator_Linear:ADP7142ARDZ-5.0 +Regulator_Linear:ADP7142AUJZ +Regulator_Linear:ADP7142AUJZ-1.8 +Regulator_Linear:ADP7142AUJZ-2.5 +Regulator_Linear:ADP7142AUJZ-3.3 +Regulator_Linear:ADP7142AUJZ-5.0 +Regulator_Linear:ADP7182AUJZ +Regulator_Linear:ADP7182AUJZ-1.8 +Regulator_Linear:ADP7182AUJZ-2.5 +Regulator_Linear:ADP7182AUJZ-3.3 +Regulator_Linear:ADP7182AUJZ-5.0 +Regulator_Linear:AMS1117 +Regulator_Linear:AMS1117-1.5 +Regulator_Linear:AMS1117-1.8 +Regulator_Linear:AMS1117-2.5 +Regulator_Linear:AMS1117-2.85 +Regulator_Linear:AMS1117-3.3 +Regulator_Linear:AMS1117-5.0 +Regulator_Linear:AMS1117CD +Regulator_Linear:AMS1117CD-1.5 +Regulator_Linear:AMS1117CD-1.8 +Regulator_Linear:AMS1117CD-2.5 +Regulator_Linear:AMS1117CD-2.85 +Regulator_Linear:AMS1117CD-3.3 +Regulator_Linear:AMS1117CD-5.0 +Regulator_Linear:AMS1117CS +Regulator_Linear:AMS1117CS-1.5 +Regulator_Linear:AMS1117CS-1.8 +Regulator_Linear:AMS1117CS-2.5 +Regulator_Linear:AMS1117CS-2.85 +Regulator_Linear:AMS1117CS-3.3 +Regulator_Linear:AMS1117CS-5.0 +Regulator_Linear:AP1117-15 +Regulator_Linear:AP1117-18 +Regulator_Linear:AP1117-25 +Regulator_Linear:AP1117-33 +Regulator_Linear:AP1117-50 +Regulator_Linear:AP1117-ADJ +Regulator_Linear:AP130-15Y +Regulator_Linear:AP130-18Y +Regulator_Linear:AP130-20Y +Regulator_Linear:AP130-25Y +Regulator_Linear:AP130-28Y +Regulator_Linear:AP130-30Y +Regulator_Linear:AP130-33Y +Regulator_Linear:AP130-35Y +Regulator_Linear:AP131-15 +Regulator_Linear:AP131-18 +Regulator_Linear:AP131-20 +Regulator_Linear:AP131-25 +Regulator_Linear:AP131-28 +Regulator_Linear:AP131-29 +Regulator_Linear:AP131-30 +Regulator_Linear:AP131-33 +Regulator_Linear:AP131-35 +Regulator_Linear:AP2112K-1.2 +Regulator_Linear:AP2112K-1.8 +Regulator_Linear:AP2112K-2.5 +Regulator_Linear:AP2112K-2.6 +Regulator_Linear:AP2112K-3.3 +Regulator_Linear:AP2127K-1.0 +Regulator_Linear:AP2127K-1.2 +Regulator_Linear:AP2127K-1.5 +Regulator_Linear:AP2127K-1.8 +Regulator_Linear:AP2127K-2.5 +Regulator_Linear:AP2127K-2.8 +Regulator_Linear:AP2127K-3.0 +Regulator_Linear:AP2127K-3.3 +Regulator_Linear:AP2127K-4.2 +Regulator_Linear:AP2127K-4.75 +Regulator_Linear:AP2127K-ADJ +Regulator_Linear:AP2127N-1.0 +Regulator_Linear:AP2127N-1.2 +Regulator_Linear:AP2127N-1.5 +Regulator_Linear:AP2127N-1.8 +Regulator_Linear:AP2127N-2.5 +Regulator_Linear:AP2127N-2.8 +Regulator_Linear:AP2127N-3.0 +Regulator_Linear:AP2127N-3.3 +Regulator_Linear:AP2127N3-1.2 +Regulator_Linear:AP2127N3-1.5 +Regulator_Linear:AP2127R-3.3 +Regulator_Linear:AP2204K-1.5 +Regulator_Linear:AP2204K-1.8 +Regulator_Linear:AP2204K-2.5 +Regulator_Linear:AP2204K-2.8 +Regulator_Linear:AP2204K-3.0 +Regulator_Linear:AP2204K-3.3 +Regulator_Linear:AP2204K-5.0 +Regulator_Linear:AP2204K-ADJ +Regulator_Linear:AP2204MP-ADJ +Regulator_Linear:AP2204R-1.5 +Regulator_Linear:AP2204R-1.8 +Regulator_Linear:AP2204R-2.5 +Regulator_Linear:AP2204R-2.8 +Regulator_Linear:AP2204R-3.0 +Regulator_Linear:AP2204R-3.3 +Regulator_Linear:AP2204R-5.0 +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 +Regulator_Linear:AP7361C-18E +Regulator_Linear:AP7361C-25E +Regulator_Linear:AP7361C-28E +Regulator_Linear:AP7361C-33E +Regulator_Linear:AP7370-12FDC +Regulator_Linear:AP7370-15FDC +Regulator_Linear:AP7370-18FDC +Regulator_Linear:AP7370-28FDC +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 +Regulator_Linear:AP7384-33SA +Regulator_Linear:AP7384-33V +Regulator_Linear:AP7384-33Y +Regulator_Linear:AP7384-50SA +Regulator_Linear:AP7384-50V +Regulator_Linear:AP7384-50Y +Regulator_Linear:AP7384-70SA +Regulator_Linear:AP7384-70V +Regulator_Linear:AP7384-70Y +Regulator_Linear:APE8865N-12-HF-3 +Regulator_Linear:APE8865N-15-HF-3 +Regulator_Linear:APE8865N-16-HF-3 +Regulator_Linear:APE8865N-17-HF-3 +Regulator_Linear:APE8865N-18-HF-3 +Regulator_Linear:APE8865N-19-HF-3 +Regulator_Linear:APE8865N-20-HF-3 +Regulator_Linear:APE8865N-21-HF-3 +Regulator_Linear:APE8865N-22-HF-3 +Regulator_Linear:APE8865N-23-HF-3 +Regulator_Linear:APE8865N-24-HF-3 +Regulator_Linear:APE8865N-25-HF-3 +Regulator_Linear:APE8865N-26-HF-3 +Regulator_Linear:APE8865N-27-HF-3 +Regulator_Linear:APE8865N-28-HF-3 +Regulator_Linear:APE8865N-29-HF-3 +Regulator_Linear:APE8865N-30-HF-3 +Regulator_Linear:APE8865N-31-HF-3 +Regulator_Linear:APE8865N-32-HF-3 +Regulator_Linear:APE8865N-33-HF-3 +Regulator_Linear:APE8865NL-12-HF-3 +Regulator_Linear:APE8865NL-15-HF-3 +Regulator_Linear:APE8865NL-16-HF-3 +Regulator_Linear:APE8865NL-17-HF-3 +Regulator_Linear:APE8865NL-18-HF-3 +Regulator_Linear:APE8865NL-19-HF-3 +Regulator_Linear:APE8865NL-20-HF-3 +Regulator_Linear:APE8865NL-21-HF-3 +Regulator_Linear:APE8865NL-22-HF-3 +Regulator_Linear:APE8865NL-23-HF-3 +Regulator_Linear:APE8865NL-24-HF-3 +Regulator_Linear:APE8865NL-25-HF-3 +Regulator_Linear:APE8865NL-26-HF-3 +Regulator_Linear:APE8865NL-27-HF-3 +Regulator_Linear:APE8865NL-28-HF-3 +Regulator_Linear:APE8865NL-29-HF-3 +Regulator_Linear:APE8865NL-30-HF-3 +Regulator_Linear:APE8865NL-31-HF-3 +Regulator_Linear:APE8865NL-32-HF-3 +Regulator_Linear:APE8865NL-33-HF-3 +Regulator_Linear:APE8865NR-12-HF-3 +Regulator_Linear:APE8865NR-15-HF-3 +Regulator_Linear:APE8865NR-16-HF-3 +Regulator_Linear:APE8865NR-17-HF-3 +Regulator_Linear:APE8865NR-18-HF-3 +Regulator_Linear:APE8865NR-19-HF-3 +Regulator_Linear:APE8865NR-20-HF-3 +Regulator_Linear:APE8865NR-21-HF-3 +Regulator_Linear:APE8865NR-22-HF-3 +Regulator_Linear:APE8865NR-23-HF-3 +Regulator_Linear:APE8865NR-24-HF-3 +Regulator_Linear:APE8865NR-25-HF-3 +Regulator_Linear:APE8865NR-26-HF-3 +Regulator_Linear:APE8865NR-27-HF-3 +Regulator_Linear:APE8865NR-28-HF-3 +Regulator_Linear:APE8865NR-29-HF-3 +Regulator_Linear:APE8865NR-30-HF-3 +Regulator_Linear:APE8865NR-31-HF-3 +Regulator_Linear:APE8865NR-32-HF-3 +Regulator_Linear:APE8865NR-33-HF-3 +Regulator_Linear:APE8865U4-12-HF-3 +Regulator_Linear:APE8865U4-15-HF-3 +Regulator_Linear:APE8865U4-16-HF-3 +Regulator_Linear:APE8865U4-17-HF-3 +Regulator_Linear:APE8865U4-18-HF-3 +Regulator_Linear:APE8865U4-19-HF-3 +Regulator_Linear:APE8865U4-20-HF-3 +Regulator_Linear:APE8865U4-21-HF-3 +Regulator_Linear:APE8865U4-22-HF-3 +Regulator_Linear:APE8865U4-23-HF-3 +Regulator_Linear:APE8865U4-24-HF-3 +Regulator_Linear:APE8865U4-25-HF-3 +Regulator_Linear:APE8865U4-26-HF-3 +Regulator_Linear:APE8865U4-27-HF-3 +Regulator_Linear:APE8865U4-28-HF-3 +Regulator_Linear:APE8865U4-29-HF-3 +Regulator_Linear:APE8865U4-30-HF-3 +Regulator_Linear:APE8865U4-31-HF-3 +Regulator_Linear:APE8865U4-32-HF-3 +Regulator_Linear:APE8865U4-33-HF-3 +Regulator_Linear:APE8865U5-12-HF-3 +Regulator_Linear:APE8865U5-15-HF-3 +Regulator_Linear:APE8865U5-16-HF-3 +Regulator_Linear:APE8865U5-17-HF-3 +Regulator_Linear:APE8865U5-18-HF-3 +Regulator_Linear:APE8865U5-19-HF-3 +Regulator_Linear:APE8865U5-20-HF-3 +Regulator_Linear:APE8865U5-21-HF-3 +Regulator_Linear:APE8865U5-22-HF-3 +Regulator_Linear:APE8865U5-23-HF-3 +Regulator_Linear:APE8865U5-24-HF-3 +Regulator_Linear:APE8865U5-25-HF-3 +Regulator_Linear:APE8865U5-26-HF-3 +Regulator_Linear:APE8865U5-27-HF-3 +Regulator_Linear:APE8865U5-28-HF-3 +Regulator_Linear:APE8865U5-29-HF-3 +Regulator_Linear:APE8865U5-30-HF-3 +Regulator_Linear:APE8865U5-31-HF-3 +Regulator_Linear:APE8865U5-32-HF-3 +Regulator_Linear:APE8865U5-33-HF-3 +Regulator_Linear:APE8865Y5-12-HF-3 +Regulator_Linear:APE8865Y5-15-HF-3 +Regulator_Linear:APE8865Y5-16-HF-3 +Regulator_Linear:APE8865Y5-17-HF-3 +Regulator_Linear:APE8865Y5-18-HF-3 +Regulator_Linear:APE8865Y5-19-HF-3 +Regulator_Linear:APE8865Y5-20-HF-3 +Regulator_Linear:APE8865Y5-21-HF-3 +Regulator_Linear:APE8865Y5-22-HF-3 +Regulator_Linear:APE8865Y5-23-HF-3 +Regulator_Linear:APE8865Y5-24-HF-3 +Regulator_Linear:APE8865Y5-25-HF-3 +Regulator_Linear:APE8865Y5-26-HF-3 +Regulator_Linear:APE8865Y5-27-HF-3 +Regulator_Linear:APE8865Y5-28-HF-3 +Regulator_Linear:APE8865Y5-29-HF-3 +Regulator_Linear:APE8865Y5-30-HF-3 +Regulator_Linear:APE8865Y5-31-HF-3 +Regulator_Linear:APE8865Y5-32-HF-3 +Regulator_Linear:APE8865Y5-33-HF-3 +Regulator_Linear:AZ1084-1.2 +Regulator_Linear:AZ1084-1.5 +Regulator_Linear:AZ1084-1.8 +Regulator_Linear:AZ1084-2.5 +Regulator_Linear:AZ1084-2.85 +Regulator_Linear:AZ1084-3.3 +Regulator_Linear:AZ1084-5.0 +Regulator_Linear:AZ1117-1.2 +Regulator_Linear:AZ1117-1.5 +Regulator_Linear:AZ1117-1.8 +Regulator_Linear:AZ1117-2.5 +Regulator_Linear:AZ1117-2.85 +Regulator_Linear:AZ1117-3.3 +Regulator_Linear:AZ1117-5.0 +Regulator_Linear:AZ1117-ADJ +Regulator_Linear:AZ1117D-ADJ +Regulator_Linear:AZ1117H-ADJ +Regulator_Linear:AZ1117R-ADJ +Regulator_Linear:AZ1117S-ADJ +Regulator_Linear:AZ1117T-ADJ +Regulator_Linear:BD00FC0WEFJ +Regulator_Linear:BD00FC0WFP +Regulator_Linear:BD15GA3WEFJ +Regulator_Linear:BD15GA5WEFJ +Regulator_Linear:BD18GA3WEFJ +Regulator_Linear:BD18GA5WEFJ +Regulator_Linear:BD25GA3WEFJ +Regulator_Linear:BD25GA5WEFJ +Regulator_Linear:BD30FC0WEFJ +Regulator_Linear:BD30FC0WFP +Regulator_Linear:BD30GA3WEFJ +Regulator_Linear:BD30GA5WEFJ +Regulator_Linear:BD33FC0FP +Regulator_Linear:BD33FC0WEFJ +Regulator_Linear:BD33FC0WFP +Regulator_Linear:BD33GA3WEFJ +Regulator_Linear:BD33GA5WEFJ +Regulator_Linear:BD50FC0FP +Regulator_Linear:BD50FC0WEFJ +Regulator_Linear:BD50FC0WFP +Regulator_Linear:BD50GA3WEFJ +Regulator_Linear:BD50GA5WEFJ +Regulator_Linear:BD60FC0WEFJ +Regulator_Linear:BD60FC0WFP +Regulator_Linear:BD60GA3WEFJ +Regulator_Linear:BD60GA5WEFJ +Regulator_Linear:BD70FC0WEFJ +Regulator_Linear:BD70FC0WFP +Regulator_Linear:BD70GA3WEFJ +Regulator_Linear:BD70GA5WEFJ +Regulator_Linear:BD80FC0WEFJ +Regulator_Linear:BD80FC0WFP +Regulator_Linear:BD80GA3WEFJ +Regulator_Linear:BD80GA5WEFJ +Regulator_Linear:BD90FC0WEFJ +Regulator_Linear:BD90FC0WFP +Regulator_Linear:BD90GA3WEFJ +Regulator_Linear:BD90GA5WEFJ +Regulator_Linear:BDJ0FC0WEFJ +Regulator_Linear:BDJ0FC0WFP +Regulator_Linear:BDJ0GA3WEFJ +Regulator_Linear:BDJ0GA5WEFJ +Regulator_Linear:BDJ2FC0WEFJ +Regulator_Linear:BDJ2FC0WFP +Regulator_Linear:BDJ2GA3WEFJ +Regulator_Linear:BDJ2GA5WEFJ +Regulator_Linear:BDJ5FC0WEFJ +Regulator_Linear:BDJ5FC0WFP +Regulator_Linear:HT75xx-1-SOT89 +Regulator_Linear:ICL7663 +Regulator_Linear:ICL7664 +Regulator_Linear:IFX25401TBV +Regulator_Linear:IFX25401TBV50 +Regulator_Linear:IFX25401TEV +Regulator_Linear:IFX25401TEV50 +Regulator_Linear:IFX27001TFV +Regulator_Linear:IFX27001TFV15 +Regulator_Linear:IFX27001TFV18 +Regulator_Linear:IFX27001TFV26 +Regulator_Linear:IFX27001TFV33 +Regulator_Linear:IFX27001TFV50 +Regulator_Linear:KA378R05 +Regulator_Linear:KA378R12C +Regulator_Linear:KA378R33 +Regulator_Linear:KA78M05_TO252 +Regulator_Linear:KF25BDT +Regulator_Linear:KF33BDT +Regulator_Linear:KF50BDT +Regulator_Linear:KF80BDT +Regulator_Linear:L200 +Regulator_Linear:L7805 +Regulator_Linear:L7806 +Regulator_Linear:L7808 +Regulator_Linear:L7809 +Regulator_Linear:L7812 +Regulator_Linear:L7815 +Regulator_Linear:L7818 +Regulator_Linear:L7824 +Regulator_Linear:L7885 +Regulator_Linear:L78L05_SO8 +Regulator_Linear:L78L05_SOT89 +Regulator_Linear:L78L05_TO92 +Regulator_Linear:L78L06_SO8 +Regulator_Linear:L78L06_SOT89 +Regulator_Linear:L78L06_TO92 +Regulator_Linear:L78L08_SO8 +Regulator_Linear:L78L08_SOT89 +Regulator_Linear:L78L08_TO92 +Regulator_Linear:L78L09_SO8 +Regulator_Linear:L78L09_SOT89 +Regulator_Linear:L78L09_TO92 +Regulator_Linear:L78L10_SO8 +Regulator_Linear:L78L10_SOT89 +Regulator_Linear:L78L10_TO92 +Regulator_Linear:L78L12_SO8 +Regulator_Linear:L78L12_SOT89 +Regulator_Linear:L78L12_TO92 +Regulator_Linear:L78L15_SO8 +Regulator_Linear:L78L15_SOT89 +Regulator_Linear:L78L15_TO92 +Regulator_Linear:L78L18_SO8 +Regulator_Linear:L78L18_SOT89 +Regulator_Linear:L78L18_TO92 +Regulator_Linear:L78L24_SO8 +Regulator_Linear:L78L24_SOT89 +Regulator_Linear:L78L24_TO92 +Regulator_Linear:L78L33_SO8 +Regulator_Linear:L78L33_SOT89 +Regulator_Linear:L78L33_TO92 +Regulator_Linear:L7905 +Regulator_Linear:L7908 +Regulator_Linear:L7912 +Regulator_Linear:L7915 +Regulator_Linear:L79L05_SO8 +Regulator_Linear:L79L05_SOT89 +Regulator_Linear:L79L05_TO92 +Regulator_Linear:L79L08_SO8 +Regulator_Linear:L79L08_SOT89 +Regulator_Linear:L79L08_TO92 +Regulator_Linear:L79L12_SO8 +Regulator_Linear:L79L12_SOT89 +Regulator_Linear:L79L12_TO92 +Regulator_Linear:L79L15_SO8 +Regulator_Linear:L79L15_SOT89 +Regulator_Linear:L79L15_TO92 +Regulator_Linear:LD0186D2T50TR +Regulator_Linear:LD1086D2M33TR +Regulator_Linear:LD1086D2MTR +Regulator_Linear:LD1086D2T12TR +Regulator_Linear:LD1086D2T18TR +Regulator_Linear:LD1086D2T33TR +Regulator_Linear:LD1086D2TTR +Regulator_Linear:LD1086DT18TR +Regulator_Linear:LD1086DT25TR +Regulator_Linear:LD1086DT33TR +Regulator_Linear:LD1086DT50TR +Regulator_Linear:LD1086DTTR +Regulator_Linear:LD1086PUR +Regulator_Linear:LD1086V-DG +Regulator_Linear:LD1086V18-DG +Regulator_Linear:LD1086V33-DG +Regulator_Linear:LD1117S12TR_SOT223 +Regulator_Linear:LD1117S18TR_SOT223 +Regulator_Linear:LD1117S25TR_SOT223 +Regulator_Linear:LD1117S33TR_SOT223 +Regulator_Linear:LD1117S50TR_SOT223 +Regulator_Linear:LD39015M08R +Regulator_Linear:LD39015M10R +Regulator_Linear:LD39015M125R +Regulator_Linear:LD39015M12R +Regulator_Linear:LD39015M15R +Regulator_Linear:LD39015M18R +Regulator_Linear:LD39015M25R +Regulator_Linear:LD39015M33R +Regulator_Linear:LD39150DT18 +Regulator_Linear:LD39150DT25 +Regulator_Linear:LD39150DT33 +Regulator_Linear:LD3985G122R_TSOT23 +Regulator_Linear:LD3985G18R_TSOT23 +Regulator_Linear:LD3985G25R_TSOT23 +Regulator_Linear:LD3985G26R_TSOT23 +Regulator_Linear:LD3985G27R_TSOT23 +Regulator_Linear:LD3985G28R_TSOT23 +Regulator_Linear:LD3985G30R_TSOT23 +Regulator_Linear:LD3985G33R_TSOT23 +Regulator_Linear:LD3985G47R_TSOT23 +Regulator_Linear:LD3985M122R_SOT23 +Regulator_Linear:LD3985M18R_SOT23 +Regulator_Linear:LD3985M25R_SOT23 +Regulator_Linear:LD3985M26R_SOT23 +Regulator_Linear:LD3985M27R_SOT23 +Regulator_Linear:LD3985M28R_SOT23 +Regulator_Linear:LD3985M29R_SOT23 +Regulator_Linear:LD3985M30R_SOT23 +Regulator_Linear:LD3985M33R_SOT23 +Regulator_Linear:LD3985M47R_SOT23 +Regulator_Linear:LDK130-08_SOT23_SOT353 +Regulator_Linear:LDK130-10_SOT23_SOT353 +Regulator_Linear:LDK130-12_SOT23_SOT353 +Regulator_Linear:LDK130-15_SOT23_SOT353 +Regulator_Linear:LDK130-18_SOT23_SOT353 +Regulator_Linear:LDK130-25_SOT23_SOT353 +Regulator_Linear:LDK130-29_SOT23_SOT353 +Regulator_Linear:LDK130-30_SOT23_SOT353 +Regulator_Linear:LDK130-32_SOT23_SOT353 +Regulator_Linear:LDK130-33_SOT23_SOT353 +Regulator_Linear:LDK130-ADJ_DFN6 +Regulator_Linear:LDK130-ADJ_SOT23_SOT353 +Regulator_Linear:LDK130PU08R_DFN6 +Regulator_Linear:LDK130PU10R_DFN6 +Regulator_Linear:LDK130PU12R_DFN6 +Regulator_Linear:LDK130PU15R_DFN6 +Regulator_Linear:LDK130PU18R_DFN6 +Regulator_Linear:LDK130PU25R_DFN6 +Regulator_Linear:LDK130PU29R_DFN6 +Regulator_Linear:LDK130PU30R_DFN6 +Regulator_Linear:LDK130PU32R_DFN6 +Regulator_Linear:LDK130PU33R_DFN6 +Regulator_Linear:LF120_TO220 +Regulator_Linear:LF120_TO252 +Regulator_Linear:LF15_TO220 +Regulator_Linear:LF15_TO252 +Regulator_Linear:LF18_TO220 +Regulator_Linear:LF18_TO252 +Regulator_Linear:LF25_TO220 +Regulator_Linear:LF25_TO252 +Regulator_Linear:LF33_TO220 +Regulator_Linear:LF33_TO252 +Regulator_Linear:LF47_TO220 +Regulator_Linear:LF47_TO252 +Regulator_Linear:LF50_TO220 +Regulator_Linear:LF50_TO252 +Regulator_Linear:LF60_TO220 +Regulator_Linear:LF60_TO252 +Regulator_Linear:LF80_TO220 +Regulator_Linear:LF80_TO252 +Regulator_Linear:LF85_TO220 +Regulator_Linear:LF85_TO252 +Regulator_Linear:LF90_TO220 +Regulator_Linear:LF90_TO252 +Regulator_Linear:LK112M15TR +Regulator_Linear:LK112M18TR +Regulator_Linear:LK112M25TR +Regulator_Linear:LK112M33TR +Regulator_Linear:LK112M50TR +Regulator_Linear:LK112M55TR +Regulator_Linear:LK112M60TR +Regulator_Linear:LK112M80TR +Regulator_Linear:LM1084-3.3 +Regulator_Linear:LM1084-5.0 +Regulator_Linear:LM1084-ADJ +Regulator_Linear:LM1085-12 +Regulator_Linear:LM1085-3.3 +Regulator_Linear:LM1085-5.0 +Regulator_Linear:LM1085-ADJ +Regulator_Linear:LM1117DT-1.8 +Regulator_Linear:LM1117DT-2.5 +Regulator_Linear:LM1117DT-3.3 +Regulator_Linear:LM1117DT-5.0 +Regulator_Linear:LM1117DT-ADJ +Regulator_Linear:LM1117LD-1.8 +Regulator_Linear:LM1117LD-2.5 +Regulator_Linear:LM1117LD-3.3 +Regulator_Linear:LM1117LD-5.0 +Regulator_Linear:LM1117LD-ADJ +Regulator_Linear:LM1117MP-1.8 +Regulator_Linear:LM1117MP-2.5 +Regulator_Linear:LM1117MP-3.3 +Regulator_Linear:LM1117MP-5.0 +Regulator_Linear:LM1117MP-ADJ +Regulator_Linear:LM1117S-3.3 +Regulator_Linear:LM1117S-5.0 +Regulator_Linear:LM1117S-ADJ +Regulator_Linear:LM1117T-2.5 +Regulator_Linear:LM1117T-3.3 +Regulator_Linear:LM1117T-5.0 +Regulator_Linear:LM1117T-ADJ +Regulator_Linear:LM117_TO3 +Regulator_Linear:LM117_TO39 +Regulator_Linear:LM137_TO3 +Regulator_Linear:LM150_TO3 +Regulator_Linear:LM2931-3.3_TO220 +Regulator_Linear:LM2931-5.0_SO8 +Regulator_Linear:LM2931-5.0_TO220 +Regulator_Linear:LM2931-ADJ_TO92 +Regulator_Linear:LM2931AZ-5.0_TO92 +Regulator_Linear:LM2936-3.0 +Regulator_Linear:LM2936-3.0_TO92 +Regulator_Linear:LM2936-3.3 +Regulator_Linear:LM2936-3.3_TO92 +Regulator_Linear:LM2936-5.0 +Regulator_Linear:LM2936-5.0_TO92 +Regulator_Linear:LM2937xMP +Regulator_Linear:LM2937xS +Regulator_Linear:LM2937xT +Regulator_Linear:LM2990SX-12 +Regulator_Linear:LM2990SX-15 +Regulator_Linear:LM2990SX-5.0 +Regulator_Linear:LM317L_SO8 +Regulator_Linear:LM317L_SOT-89 +Regulator_Linear:LM317L_TO92 +Regulator_Linear:LM317_SOT-223 +Regulator_Linear:LM317_TO-220 +Regulator_Linear:LM317_TO-252 +Regulator_Linear:LM317_TO-263 +Regulator_Linear:LM317_TO3 +Regulator_Linear:LM317_TO39 +Regulator_Linear:LM337L_SO8 +Regulator_Linear:LM337L_TO92 +Regulator_Linear:LM337_SOT223 +Regulator_Linear:LM337_TO220 +Regulator_Linear:LM337_TO252 +Regulator_Linear:LM337_TO263 +Regulator_Linear:LM337_TO3 +Regulator_Linear:LM337_TO39 +Regulator_Linear:LM341T-05_TO220 +Regulator_Linear:LM341T-12_TO220 +Regulator_Linear:LM341T-15_TO220 +Regulator_Linear:LM3480-12 +Regulator_Linear:LM3480-15 +Regulator_Linear:LM3480-3.3 +Regulator_Linear:LM3480-5.0 +Regulator_Linear:LM350_TO220 +Regulator_Linear:LM350_TO3 +Regulator_Linear:LM723_DIP14 +Regulator_Linear:LM723_TO100 +Regulator_Linear:LM7805_TO220 +Regulator_Linear:LM7806_TO220 +Regulator_Linear:LM7808_TO220 +Regulator_Linear:LM7809_TO220 +Regulator_Linear:LM7810_TO220 +Regulator_Linear:LM7812_TO220 +Regulator_Linear:LM7815_TO220 +Regulator_Linear:LM7818_TO220 +Regulator_Linear:LM7824_TO220 +Regulator_Linear:LM78L05_SO8 +Regulator_Linear:LM78L05_TO92 +Regulator_Linear:LM78L12_SO8 +Regulator_Linear:LM78L12_TO92 +Regulator_Linear:LM78M05_TO220 +Regulator_Linear:LM78M05_TO252 +Regulator_Linear:LM7905_TO220 +Regulator_Linear:LM7906_TO220 +Regulator_Linear:LM7908_TO220 +Regulator_Linear:LM7909_TO220 +Regulator_Linear:LM7910_TO220 +Regulator_Linear:LM7912_TO220 +Regulator_Linear:LM7915_TO220 +Regulator_Linear:LM7918_TO220 +Regulator_Linear:LM7924_TO220 +Regulator_Linear:LM79L05_TO92 +Regulator_Linear:LM79L12_TO92 +Regulator_Linear:LM79L15_TO92 +Regulator_Linear:LM79M05_TO220 +Regulator_Linear:LM79M12_TO220 +Regulator_Linear:LM79M15_TO220 +Regulator_Linear:LP2950-3.0_TO252 +Regulator_Linear:LP2950-3.0_TO92 +Regulator_Linear:LP2950-3.3_TO252 +Regulator_Linear:LP2950-3.3_TO92 +Regulator_Linear:LP2950-5.0_TO252 +Regulator_Linear:LP2950-5.0_TO92 +Regulator_Linear:LP2951-3.0_DIP +Regulator_Linear:LP2951-3.0_SOIC +Regulator_Linear:LP2951-3.0_VSSOP +Regulator_Linear:LP2951-3.0_WSON +Regulator_Linear:LP2951-3.3_DIP +Regulator_Linear:LP2951-3.3_SOIC +Regulator_Linear:LP2951-3.3_VSSOP +Regulator_Linear:LP2951-3.3_WSON +Regulator_Linear:LP2951-5.0_DIP +Regulator_Linear:LP2951-5.0_SOIC +Regulator_Linear:LP2951-5.0_VSSOP +Regulator_Linear:LP2951-5.0_WSON +Regulator_Linear:LP2980-ADJ +Regulator_Linear:LP2985-1.8 +Regulator_Linear:LP2985-10.0 +Regulator_Linear:LP2985-2.5 +Regulator_Linear:LP2985-2.8 +Regulator_Linear:LP2985-2.9 +Regulator_Linear:LP2985-3.0 +Regulator_Linear:LP2985-3.1 +Regulator_Linear:LP2985-3.3 +Regulator_Linear:LP2985-5.0 +Regulator_Linear:LP2987-3.0_SOIC8_VSSOP8 +Regulator_Linear:LP2987-3.0_WSON8 +Regulator_Linear:LP2987-3.3_SOIC8_VSSOP8 +Regulator_Linear:LP2987-3.3_WSON8 +Regulator_Linear:LP2987-5.0_SOIC8_VSSOP8 +Regulator_Linear:LP2987-5.0_WSON8 +Regulator_Linear:LP2988-2.8_SOIC8_VSSOP8 +Regulator_Linear:LP2988-2.8_WSON8 +Regulator_Linear:LP2988-3.0_SOIC8_VSSOP8 +Regulator_Linear:LP2988-3.0_WSON8 +Regulator_Linear:LP2988-3.3_SOIC8_VSSOP8 +Regulator_Linear:LP2988-3.3_WSON8 +Regulator_Linear:LP2988-3.8_SOIC8_VSSOP8 +Regulator_Linear:LP2988-3.8_WSON8 +Regulator_Linear:LP2988-5.0_SOIC8_VSSOP8 +Regulator_Linear:LP2988-5.0_WSON8 +Regulator_Linear:LP38512MR-ADJ +Regulator_Linear:LP38512TJ-1.8 +Regulator_Linear:LP38512TJ-ADJ +Regulator_Linear:LP38512TS-1.8 +Regulator_Linear:LP38691SD-1.8 +Regulator_Linear:LP38691SD-2.5 +Regulator_Linear:LP38691SD-3.3 +Regulator_Linear:LP38691SD-5.0 +Regulator_Linear:LP38693DT-1.8 +Regulator_Linear:LP38693DT-2.5 +Regulator_Linear:LP38693DT-3.3 +Regulator_Linear:LP38693DT-5.0 +Regulator_Linear:LP38693MP-1.8 +Regulator_Linear:LP38693MP-2.5 +Regulator_Linear:LP38693MP-3.3 +Regulator_Linear:LP38693MP-5.0 +Regulator_Linear:LP38693SD-1.8 +Regulator_Linear:LP38693SD-2.5 +Regulator_Linear:LP38693SD-3.3 +Regulator_Linear:LP38693SD-5.0 +Regulator_Linear:LP3963-1.8 +Regulator_Linear:LP3963-2.5 +Regulator_Linear:LP3963-3.3 +Regulator_Linear:LP3966-1.8 +Regulator_Linear:LP3966-2.5 +Regulator_Linear:LP3966-3.3 +Regulator_Linear:LP3966-ADJ +Regulator_Linear:LP3982ILD-1.8 +Regulator_Linear:LP3982ILD-2.5 +Regulator_Linear:LP3982ILD-3.0 +Regulator_Linear:LP3982ILD-3.3 +Regulator_Linear:LP3982ILD-ADJ +Regulator_Linear:LP3982IMM-1.8 +Regulator_Linear:LP3982IMM-2.5 +Regulator_Linear:LP3982IMM-3.0 +Regulator_Linear:LP3982IMM-3.3 +Regulator_Linear:LP3982IMM-ADJ +Regulator_Linear:LP3982IMMX-2.82 +Regulator_Linear:LP3987H-33B5 +Regulator_Linear:LP3992-18B5 +Regulator_Linear:LP5907MFX-1.2 +Regulator_Linear:LP5907MFX-1.5 +Regulator_Linear:LP5907MFX-1.8 +Regulator_Linear:LP5907MFX-2.5 +Regulator_Linear:LP5907MFX-2.8 +Regulator_Linear:LP5907MFX-2.85 +Regulator_Linear:LP5907MFX-2.9 +Regulator_Linear:LP5907MFX-3.0 +Regulator_Linear:LP5907MFX-3.1 +Regulator_Linear:LP5907MFX-3.2 +Regulator_Linear:LP5907MFX-3.3 +Regulator_Linear:LP5907MFX-4.5 +Regulator_Linear:LP5912-0.9DRV +Regulator_Linear:LP5912-1.0DRV +Regulator_Linear:LP5912-1.1DRV +Regulator_Linear:LP5912-1.2DRV +Regulator_Linear:LP5912-1.5DRV +Regulator_Linear:LP5912-1.8DRV +Regulator_Linear:LP5912-2.5DRV +Regulator_Linear:LP5912-2.8DRV +Regulator_Linear:LP5912-3.0DRV +Regulator_Linear:LP5912-3.3DRV +Regulator_Linear:LP5912-5.0DRV +Regulator_Linear:LR8K4-G +Regulator_Linear:LR8N3-G +Regulator_Linear:LR8N8-G +Regulator_Linear:LT1033C +Regulator_Linear:LT1083-12 +Regulator_Linear:LT1083-3.3 +Regulator_Linear:LT1083-3.6 +Regulator_Linear:LT1083-5.0 +Regulator_Linear:LT1083-ADJ +Regulator_Linear:LT1084-12 +Regulator_Linear:LT1084-3.3 +Regulator_Linear:LT1084-3.6 +Regulator_Linear:LT1084-5.0 +Regulator_Linear:LT1084-ADJ +Regulator_Linear:LT1085-12 +Regulator_Linear:LT1085-3.3 +Regulator_Linear:LT1085-3.6 +Regulator_Linear:LT1085-5.0 +Regulator_Linear:LT1085-ADJ +Regulator_Linear:LT1086-12 +Regulator_Linear:LT1086-2.85 +Regulator_Linear:LT1086-3.3 +Regulator_Linear:LT1086-3.6 +Regulator_Linear:LT1086-5.0 +Regulator_Linear:LT1086-ADJ +Regulator_Linear:LT1117-2.85 +Regulator_Linear:LT1117-3.3 +Regulator_Linear:LT1117-5.0 +Regulator_Linear:LT1117-ADJ +Regulator_Linear:LT1129-3.3_SO8 +Regulator_Linear:LT1129-3.3_SOT223 +Regulator_Linear:LT1129-3.3_TO220_TO263 +Regulator_Linear:LT1129-5.0_SO8 +Regulator_Linear:LT1129-5.0_SOT223 +Regulator_Linear:LT1129-5.0_TO220_TO263 +Regulator_Linear:LT1129-ADJ_SO8 +Regulator_Linear:LT1129-ADJ_TO220_TO263 +Regulator_Linear:LT1175-5_SO8_DIP8 +Regulator_Linear:LT1175-5_SOT223 +Regulator_Linear:LT1175-5_TO263_TO220 +Regulator_Linear:LT1175-ADJ_SO8_DIP8 +Regulator_Linear:LT1175-ADJ_SOT223 +Regulator_Linear:LT1175-ADJ_TO263_TO220 +Regulator_Linear:LT1584-3.3 +Regulator_Linear:LT1584-3.38 +Regulator_Linear:LT1584-3.45 +Regulator_Linear:LT1584-3.6 +Regulator_Linear:LT1584-ADJ +Regulator_Linear:LT1585-3.3 +Regulator_Linear:LT1585-3.38 +Regulator_Linear:LT1585-3.45 +Regulator_Linear:LT1585-3.6 +Regulator_Linear:LT1585-ADJ +Regulator_Linear:LT1587-3.3 +Regulator_Linear:LT1587-3.45 +Regulator_Linear:LT1587-3.6 +Regulator_Linear:LT1587-ADJ +Regulator_Linear:LT1761-1.2 +Regulator_Linear:LT1761-1.5 +Regulator_Linear:LT1761-1.8 +Regulator_Linear:LT1761-2 +Regulator_Linear:LT1761-2.5 +Regulator_Linear:LT1761-2.8 +Regulator_Linear:LT1761-3 +Regulator_Linear:LT1761-3.3 +Regulator_Linear:LT1761-5 +Regulator_Linear:LT1761-BYP +Regulator_Linear:LT1761-SD +Regulator_Linear:LT1762 +Regulator_Linear:LT1762-2.5 +Regulator_Linear:LT1762-3 +Regulator_Linear:LT1762-3.3 +Regulator_Linear:LT1762-5 +Regulator_Linear:LT1962 +Regulator_Linear:LT1962-1.5 +Regulator_Linear:LT1962-1.8 +Regulator_Linear:LT1962-2.5 +Regulator_Linear:LT1962-3 +Regulator_Linear:LT1962-3.3 +Regulator_Linear:LT1962-5 +Regulator_Linear:LT1963AEQ +Regulator_Linear:LT1963AxQ-1.5 +Regulator_Linear:LT1963AxQ-1.8 +Regulator_Linear:LT1963AxQ-2.5 +Regulator_Linear:LT1963AxQ-3.3 +Regulator_Linear:LT1963AxST-1.5 +Regulator_Linear:LT1963AxST-1.8 +Regulator_Linear:LT1963AxST-2.5 +Regulator_Linear:LT1963AxST-3.3 +Regulator_Linear:LT1963EQ +Regulator_Linear:LT1964-5 +Regulator_Linear:LT1964-BYP +Regulator_Linear:LT1964-SD +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 +Regulator_Linear:LT3015xQ-2.5 +Regulator_Linear:LT3015xQ-3 +Regulator_Linear:LT3015xQ-3.3 +Regulator_Linear:LT3015xQ-5 +Regulator_Linear:LT3032 +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 +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 +Regulator_Linear:MAX5093BATE +Regulator_Linear:MAX603 +Regulator_Linear:MAX604 +Regulator_Linear:MAX663 +Regulator_Linear:MAX664 +Regulator_Linear:MAX666 +Regulator_Linear:MAX882 +Regulator_Linear:MAX883 +Regulator_Linear:MAX884 +Regulator_Linear:MC78L05_SO8 +Regulator_Linear:MC78L05_SOT89 +Regulator_Linear:MC78L05_TO92 +Regulator_Linear:MC78L06_SO8 +Regulator_Linear:MC78L06_TO92 +Regulator_Linear:MC78L08_SO8 +Regulator_Linear:MC78L08_SOT89 +Regulator_Linear:MC78L08_TO92 +Regulator_Linear:MC78L12_SO8 +Regulator_Linear:MC78L12_SOT89 +Regulator_Linear:MC78L12_TO92 +Regulator_Linear:MC78L15_SO8 +Regulator_Linear:MC78L15_TO92 +Regulator_Linear:MC78L18_SO8 +Regulator_Linear:MC78L18_TO92 +Regulator_Linear:MC78L24_SO8 +Regulator_Linear:MC78L24_TO92 +Regulator_Linear:MC78LC18NTR +Regulator_Linear:MC78LC25NTR +Regulator_Linear:MC78LC30NTR +Regulator_Linear:MC78LC33NTR +Regulator_Linear:MC78LC50NTR +Regulator_Linear:MC78M05_TO252 +Regulator_Linear:MC7905 +Regulator_Linear:MC7905.2 +Regulator_Linear:MC7906 +Regulator_Linear:MC7908 +Regulator_Linear:MC7912 +Regulator_Linear:MC7915 +Regulator_Linear:MC7918 +Regulator_Linear:MC7924 +Regulator_Linear:MC79L05_SO8 +Regulator_Linear:MC79L12_SO8 +Regulator_Linear:MC79L15_SO8 +Regulator_Linear:MC79M05_TO220 +Regulator_Linear:MC79M05_TO252 +Regulator_Linear:MC79M08_TO220 +Regulator_Linear:MC79M08_TO252 +Regulator_Linear:MC79M12_TO220 +Regulator_Linear:MC79M12_TO252 +Regulator_Linear:MC79M15_TO220 +Regulator_Linear:MC79M15_TO252 +Regulator_Linear:MCP1700x-120xxMB +Regulator_Linear:MCP1700x-120xxTO +Regulator_Linear:MCP1700x-120xxTT +Regulator_Linear:MCP1700x-180xxMB +Regulator_Linear:MCP1700x-180xxTO +Regulator_Linear:MCP1700x-180xxTT +Regulator_Linear:MCP1700x-250xxMB +Regulator_Linear:MCP1700x-250xxTO +Regulator_Linear:MCP1700x-250xxTT +Regulator_Linear:MCP1700x-280xxMB +Regulator_Linear:MCP1700x-280xxTO +Regulator_Linear:MCP1700x-280xxTT +Regulator_Linear:MCP1700x-300xxMB +Regulator_Linear:MCP1700x-300xxTO +Regulator_Linear:MCP1700x-300xxTT +Regulator_Linear:MCP1700x-330xxMB +Regulator_Linear:MCP1700x-330xxTO +Regulator_Linear:MCP1700x-330xxTT +Regulator_Linear:MCP1700x-500xxMB +Regulator_Linear:MCP1700x-500xxTO +Regulator_Linear:MCP1700x-500xxTT +Regulator_Linear:MCP1703Ax-120xxDB +Regulator_Linear:MCP1703Ax-120xxMB +Regulator_Linear:MCP1703Ax-120xxTT +Regulator_Linear:MCP1703Ax-150xxDB +Regulator_Linear:MCP1703Ax-150xxMB +Regulator_Linear:MCP1703Ax-150xxTT +Regulator_Linear:MCP1703Ax-180xxDB +Regulator_Linear:MCP1703Ax-180xxMB +Regulator_Linear:MCP1703Ax-180xxTT +Regulator_Linear:MCP1703Ax-250xxDB +Regulator_Linear:MCP1703Ax-250xxMB +Regulator_Linear:MCP1703Ax-250xxTT +Regulator_Linear:MCP1703Ax-280xxDB +Regulator_Linear:MCP1703Ax-280xxMB +Regulator_Linear:MCP1703Ax-280xxTT +Regulator_Linear:MCP1703Ax-300xxDB +Regulator_Linear:MCP1703Ax-300xxMB +Regulator_Linear:MCP1703Ax-300xxTT +Regulator_Linear:MCP1703Ax-330xxDB +Regulator_Linear:MCP1703Ax-330xxMB +Regulator_Linear:MCP1703Ax-330xxTT +Regulator_Linear:MCP1703Ax-400xxDB +Regulator_Linear:MCP1703Ax-400xxMB +Regulator_Linear:MCP1703Ax-400xxTT +Regulator_Linear:MCP1703Ax-500xxDB +Regulator_Linear:MCP1703Ax-500xxMB +Regulator_Linear:MCP1703Ax-500xxTT +Regulator_Linear:MCP1727-0802xMF +Regulator_Linear:MCP1727-0802xSN +Regulator_Linear:MCP1727-1202xMF +Regulator_Linear:MCP1727-1202xSN +Regulator_Linear:MCP1727-1802xMF +Regulator_Linear:MCP1727-1802xSN +Regulator_Linear:MCP1727-2502xMF +Regulator_Linear:MCP1727-2502xSN +Regulator_Linear:MCP1727-3002xMF +Regulator_Linear:MCP1727-3002xSN +Regulator_Linear:MCP1727-3302xMF +Regulator_Linear:MCP1727-3302xSN +Regulator_Linear:MCP1727-5002xMF +Regulator_Linear:MCP1727-5002xSN +Regulator_Linear:MCP1727-ADJxMF +Regulator_Linear:MCP1727-ADJxSN +Regulator_Linear:MCP1754S-1802xCB +Regulator_Linear:MCP1754S-1802xMB +Regulator_Linear:MCP1754S-3302xCB +Regulator_Linear:MCP1754S-3302xMB +Regulator_Linear:MCP1754S-5002xCB +Regulator_Linear:MCP1754S-5002xMB +Regulator_Linear:MCP1792x-3302xCB +Regulator_Linear:MCP1792x-3302xDB +Regulator_Linear:MCP1792x-4102xCB +Regulator_Linear:MCP1792x-4102xDB +Regulator_Linear:MCP1792x-5002xCB +Regulator_Linear:MCP1792x-5002xDB +Regulator_Linear:MCP1799x-330xxTT +Regulator_Linear:MCP1799x-500xxTT +Regulator_Linear:MCP1802x-xx02xOT +Regulator_Linear:MCP1804x-1802xDB +Regulator_Linear:MCP1804x-1802xMB +Regulator_Linear:MCP1804x-1802xMT +Regulator_Linear:MCP1804x-1802xOT +Regulator_Linear:MCP1804x-2502xDB +Regulator_Linear:MCP1804x-2502xMB +Regulator_Linear:MCP1804x-2502xMT +Regulator_Linear:MCP1804x-2502xOT +Regulator_Linear:MCP1804x-3002xDB +Regulator_Linear:MCP1804x-3002xMB +Regulator_Linear:MCP1804x-3002xMT +Regulator_Linear:MCP1804x-3002xOT +Regulator_Linear:MCP1804x-3302xDB +Regulator_Linear:MCP1804x-3302xMB +Regulator_Linear:MCP1804x-3302xMT +Regulator_Linear:MCP1804x-3302xOT +Regulator_Linear:MCP1804x-5002xDB +Regulator_Linear:MCP1804x-5002xMB +Regulator_Linear:MCP1804x-5002xMT +Regulator_Linear:MCP1804x-5002xOT +Regulator_Linear:MCP1804x-A002xDB +Regulator_Linear:MCP1804x-A002xMB +Regulator_Linear:MCP1804x-A002xMT +Regulator_Linear:MCP1804x-A002xOT +Regulator_Linear:MCP1804x-C002xDB +Regulator_Linear:MCP1804x-C002xMB +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 +Regulator_Linear:MIC29153WU +Regulator_Linear:MIC29302AWD +Regulator_Linear:MIC29302AWU +Regulator_Linear:MIC29302WT +Regulator_Linear:MIC29302WU +Regulator_Linear:MIC29303WT +Regulator_Linear:MIC29303WU +Regulator_Linear:MIC29502WT +Regulator_Linear:MIC29502WU +Regulator_Linear:MIC29503WT +Regulator_Linear:MIC29503WU +Regulator_Linear:MIC29752WT +Regulator_Linear:MIC5205-2.5YM5 +Regulator_Linear:MIC5205-2.7YM5 +Regulator_Linear:MIC5205-2.85YM5 +Regulator_Linear:MIC5205-2.8YM5 +Regulator_Linear:MIC5205-2.9YM5 +Regulator_Linear:MIC5205-3.0YM5 +Regulator_Linear:MIC5205-3.1YM5 +Regulator_Linear:MIC5205-3.2YM5 +Regulator_Linear:MIC5205-3.3YM5 +Regulator_Linear:MIC5205-3.6YM5 +Regulator_Linear:MIC5205-3.8YM5 +Regulator_Linear:MIC5205-4.0YM5 +Regulator_Linear:MIC5205-5.0YM5 +Regulator_Linear:MIC5205YM5 +Regulator_Linear:MIC5219-2.5YM5 +Regulator_Linear:MIC5219-2.5YMM +Regulator_Linear:MIC5219-2.6YM5 +Regulator_Linear:MIC5219-2.7YM5 +Regulator_Linear:MIC5219-2.85YM5 +Regulator_Linear:MIC5219-2.85YMM +Regulator_Linear:MIC5219-2.8YM5 +Regulator_Linear:MIC5219-2.9YM5 +Regulator_Linear:MIC5219-3.0YM5 +Regulator_Linear:MIC5219-3.0YMM +Regulator_Linear:MIC5219-3.1YM5 +Regulator_Linear:MIC5219-3.3YM5 +Regulator_Linear:MIC5219-3.3YMM +Regulator_Linear:MIC5219-3.6YM5 +Regulator_Linear:MIC5219-3.6YMM +Regulator_Linear:MIC5219-5.0YM5 +Regulator_Linear:MIC5219-5.0YMM +Regulator_Linear:MIC5219YM5 +Regulator_Linear:MIC5219YMM +Regulator_Linear:MIC5317-1.0xM5 +Regulator_Linear:MIC5317-1.2xM5 +Regulator_Linear:MIC5317-1.5xM5 +Regulator_Linear:MIC5317-1.8xM5 +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 +Regulator_Linear:MIC5353-2.8YMT +Regulator_Linear:MIC5353-3.0YMT +Regulator_Linear:MIC5353-3.3YMT +Regulator_Linear:MIC5353YMT +Regulator_Linear:MIC5355-G4YMME +Regulator_Linear:MIC5355-JGYMME +Regulator_Linear:MIC5355-S4YMME +Regulator_Linear:MIC5355-SCYMME +Regulator_Linear:MIC5355-SGYMME +Regulator_Linear:MIC5356-G4YMME +Regulator_Linear:MIC5356-JGYMME +Regulator_Linear:MIC5356-MGYML +Regulator_Linear:MIC5356-MMYML +Regulator_Linear:MIC5356-S4YMME +Regulator_Linear:MIC5356-SCYMME +Regulator_Linear:MIC5356-SGYMME +Regulator_Linear:MIC5365-3.3YC5 +Regulator_Linear:MIC5365-3.3YD5 +Regulator_Linear:MIC5366-3.3YC5 +Regulator_Linear:MIC5501-3.0YM5 +Regulator_Linear:MIC5504-1.2YM5 +Regulator_Linear:MIC5504-1.8YM5 +Regulator_Linear:MIC5504-2.5YM5 +Regulator_Linear:MIC5504-2.8YM5 +Regulator_Linear:MIC5504-3.3YM5 +Regulator_Linear:NCP1117-1.5_SOT223 +Regulator_Linear:NCP1117-1.5_TO252 +Regulator_Linear:NCP1117-1.8_SOT223 +Regulator_Linear:NCP1117-1.8_TO252 +Regulator_Linear:NCP1117-1.9_TO252 +Regulator_Linear:NCP1117-12_SOT223 +Regulator_Linear:NCP1117-12_TO252 +Regulator_Linear:NCP1117-2.0_SOT223 +Regulator_Linear:NCP1117-2.0_TO252 +Regulator_Linear:NCP1117-2.5_SOT223 +Regulator_Linear:NCP1117-2.5_TO252 +Regulator_Linear:NCP1117-2.85_SOT223 +Regulator_Linear:NCP1117-2.85_TO252 +Regulator_Linear:NCP1117-3.3_SOT223 +Regulator_Linear:NCP1117-3.3_TO252 +Regulator_Linear:NCP1117-5.0_SOT223 +Regulator_Linear:NCP1117-5.0_TO252 +Regulator_Linear:NCP1117-ADJ_SOT223 +Regulator_Linear:NCP1117-ADJ_TO252 +Regulator_Linear:NCP115AMX120TCG +Regulator_Linear:NCP115AMX250TCG +Regulator_Linear:NCP133AMX100TCG +Regulator_Linear:NCP163AFCS120T2G +Regulator_Linear:NCP163AFCS180T2G +Regulator_Linear:NCP163AFCS250T2G +Regulator_Linear:NCP163AFCS260T2G +Regulator_Linear:NCP163AFCS270T2G +Regulator_Linear:NCP163AFCS280T2G +Regulator_Linear:NCP163AFCS285T2G +Regulator_Linear:NCP163AFCS290T2G +Regulator_Linear:NCP163AFCS2925T2G +Regulator_Linear:NCP163AFCS514T2G +Regulator_Linear:NCP163AFCT120T2G +Regulator_Linear:NCP163AFCT180T2G +Regulator_Linear:NCP163AFCT250T2G +Regulator_Linear:NCP163AFCT260T2G +Regulator_Linear:NCP163AFCT270T2G +Regulator_Linear:NCP163AFCT280T2G +Regulator_Linear:NCP163AFCT285T2G +Regulator_Linear:NCP163AFCT290T2G +Regulator_Linear:NCP163AFCT2925T2G +Regulator_Linear:NCP163AFCT300T2G +Regulator_Linear:NCP163AFCT330T2G +Regulator_Linear:NCP163AFCT514T2G +Regulator_Linear:NCP163ASN150T1G +Regulator_Linear:NCP163ASN180T1G +Regulator_Linear:NCP163ASN250T1G +Regulator_Linear:NCP163ASN270T1G +Regulator_Linear:NCP163ASN280T1G +Regulator_Linear:NCP163ASN300T1G +Regulator_Linear:NCP163ASN330T1G +Regulator_Linear:NCP163ASN350T1G +Regulator_Linear:NCP163ASN500T1G +Regulator_Linear:NCP163BFCS180T2G +Regulator_Linear:NCP163BFCS2925T2G +Regulator_Linear:NCP163BFCT180T2G +Regulator_Linear:NCP163CFCS285T2G +Regulator_Linear:NCP662SQ15 +Regulator_Linear:NCP662SQ18 +Regulator_Linear:NCP662SQ33 +Regulator_Linear:NCP662SQ50 +Regulator_Linear:NCP718xSN120 +Regulator_Linear:NCP718xSN150 +Regulator_Linear:NCP718xSN180 +Regulator_Linear:NCP718xSN250 +Regulator_Linear:NCP718xSN300 +Regulator_Linear:NCP718xSN330 +Regulator_Linear:NCP718xSN500 +Regulator_Linear:NCV8114ASN120T1G +Regulator_Linear:NCV8114ASN150T1G +Regulator_Linear:NCV8114ASN165T1G +Regulator_Linear:NCV8114ASN180T1G +Regulator_Linear:NCV8114ASN250T1G +Regulator_Linear:NCV8114ASN280T1G +Regulator_Linear:NCV8114ASN300T1G +Regulator_Linear:NCV8114ASN330T1G +Regulator_Linear:NCV8114BSN120T1G +Regulator_Linear:NCV8114BSN150T1G +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 +Regulator_Linear:SPX2920M3-3.3_SOT223 +Regulator_Linear:SPX2920M3-5.0_SOT223 +Regulator_Linear:SPX2920S-3.3_SO8 +Regulator_Linear:SPX2920S-5.0_SO8 +Regulator_Linear:SPX2920T-3.3_TO263 +Regulator_Linear:SPX2920T-5.0_TO263 +Regulator_Linear:SPX2920U-3.3_TO220 +Regulator_Linear:SPX2920U-5.0_TO220 +Regulator_Linear:SPX3819M5-L +Regulator_Linear:SPX3819M5-L-1-2 +Regulator_Linear:SPX3819M5-L-1-5 +Regulator_Linear:SPX3819M5-L-1-8 +Regulator_Linear:SPX3819M5-L-2-5 +Regulator_Linear:SPX3819M5-L-3-0 +Regulator_Linear:SPX3819M5-L-3-3 +Regulator_Linear:SPX3819M5-L-5-0 +Regulator_Linear:TC1014-xCT +Regulator_Linear:TC1015-xCT +Regulator_Linear:TC1017-xCT +Regulator_Linear:TC1017-xLT +Regulator_Linear:TC1017R-xLT +Regulator_Linear:TC1054 +Regulator_Linear:TC1055 +Regulator_Linear:TC1185-xCT +Regulator_Linear:TC1186 +Regulator_Linear:TC1262-25 +Regulator_Linear:TC1262-28 +Regulator_Linear:TC1262-30 +Regulator_Linear:TC1262-33 +Regulator_Linear:TC1262-50 +Regulator_Linear:TC2014-1.8VxCTTR +Regulator_Linear:TC2014-2.5VxCTTR +Regulator_Linear:TC2014-2.6VxCTTR +Regulator_Linear:TC2014-2.7VxCTTR +Regulator_Linear:TC2014-2.85VxCTTR +Regulator_Linear:TC2014-2.8VxCTTR +Regulator_Linear:TC2014-3.0VxCTTR +Regulator_Linear:TC2014-3.3VxCTTR +Regulator_Linear:TC2014-5.0VxCTTR +Regulator_Linear:TC2015-1.8VxCTTR +Regulator_Linear:TC2015-2.5VxCTTR +Regulator_Linear:TC2015-2.6VxCTTR +Regulator_Linear:TC2015-2.7VxCTTR +Regulator_Linear:TC2015-2.85VxCTTR +Regulator_Linear:TC2015-2.8VxCTTR +Regulator_Linear:TC2015-3.0VxCTTR +Regulator_Linear:TC2015-3.3VxCTTR +Regulator_Linear:TC2015-5.0VxCTTR +Regulator_Linear:TC2185-1.8VxCTTR +Regulator_Linear:TC2185-2.5VxCTTR +Regulator_Linear:TC2185-2.6VxCTTR +Regulator_Linear:TC2185-2.7VxCTTR +Regulator_Linear:TC2185-2.85VxCTTR +Regulator_Linear:TC2185-2.8VxCTTR +Regulator_Linear:TC2185-3.0VxCTTR +Regulator_Linear:TC2185-3.3VxCTTR +Regulator_Linear:TC2185-5.0VxCTTR +Regulator_Linear:TCR2EE10 +Regulator_Linear:TCR2EE105 +Regulator_Linear:TCR2EE11 +Regulator_Linear:TCR2EE115 +Regulator_Linear:TCR2EE12 +Regulator_Linear:TCR2EE125 +Regulator_Linear:TCR2EE13 +Regulator_Linear:TCR2EE135 +Regulator_Linear:TCR2EE14 +Regulator_Linear:TCR2EE145 +Regulator_Linear:TCR2EE15 +Regulator_Linear:TCR2EE17 +Regulator_Linear:TCR2EE18 +Regulator_Linear:TCR2EE185 +Regulator_Linear:TCR2EE19 +Regulator_Linear:TCR2EE20 +Regulator_Linear:TCR2EE24 +Regulator_Linear:TCR2EE25 +Regulator_Linear:TCR2EE27 +Regulator_Linear:TCR2EE275 +Regulator_Linear:TLV1117-15 +Regulator_Linear:TLV1117-18 +Regulator_Linear:TLV1117-25 +Regulator_Linear:TLV1117-33 +Regulator_Linear:TLV1117-50 +Regulator_Linear:TLV1117-ADJ +Regulator_Linear:TLV70012_SOT23-5 +Regulator_Linear:TLV70012_SOT353 +Regulator_Linear:TLV70012_WSON6 +Regulator_Linear:TLV70013_SOT23-5 +Regulator_Linear:TLV70015_SOT23-5 +Regulator_Linear:TLV70015_SOT353 +Regulator_Linear:TLV70015_WSON6 +Regulator_Linear:TLV70018_SOT23-5 +Regulator_Linear:TLV70018_SOT353 +Regulator_Linear:TLV70018_WSON6 +Regulator_Linear:TLV70019_SOT23-5 +Regulator_Linear:TLV70025_SOT23-5 +Regulator_Linear:TLV70025_SOT353 +Regulator_Linear:TLV70025_WSON6 +Regulator_Linear:TLV70028_SOT353 +Regulator_Linear:TLV70028_WSON6 +Regulator_Linear:TLV70029_WSON6 +Regulator_Linear:TLV70030_SOT23-5 +Regulator_Linear:TLV70030_SOT353 +Regulator_Linear:TLV70030_WSON6 +Regulator_Linear:TLV70031_WSON6 +Regulator_Linear:TLV70032_SOT23-5 +Regulator_Linear:TLV70033_SOT23-5 +Regulator_Linear:TLV70033_SOT353 +Regulator_Linear:TLV70033_WSON6 +Regulator_Linear:TLV70036_SOT23-5 +Regulator_Linear:TLV70036_WSON6 +Regulator_Linear:TLV70212_SOT23-5 +Regulator_Linear:TLV70215_SOT23-5 +Regulator_Linear:TLV70218_SOT23-5 +Regulator_Linear:TLV70225_SOT23-5 +Regulator_Linear:TLV70225_WSON6 +Regulator_Linear:TLV70228_SOT23-5 +Regulator_Linear:TLV70228_WSON6 +Regulator_Linear:TLV70229_WSON6 +Regulator_Linear:TLV70230_SOT23-5 +Regulator_Linear:TLV70231_SOT23-5 +Regulator_Linear:TLV70233_SOT23-5 +Regulator_Linear:TLV70233_WSON6 +Regulator_Linear:TLV70235_SOT23-5 +Regulator_Linear:TLV70236_WSON6 +Regulator_Linear:TLV70237_SOT23-5 +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 +Regulator_Linear:TLV71310PDBV +Regulator_Linear:TLV71311PDBV +Regulator_Linear:TLV71312PDBV +Regulator_Linear:TLV71315PDBV +Regulator_Linear:TLV713185PDBV +Regulator_Linear:TLV71318PDBV +Regulator_Linear:TLV71325PDBV +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 +Regulator_Linear:TLV73315PDBV +Regulator_Linear:TLV73318PDBV +Regulator_Linear:TLV73325PDBV +Regulator_Linear:TLV733285PDBV +Regulator_Linear:TLV73328PDBV +Regulator_Linear:TLV73330PDBV +Regulator_Linear:TLV73333PDBV +Regulator_Linear:TLV75509PDBV +Regulator_Linear:TLV75509PDRV +Regulator_Linear:TLV75510PDBV +Regulator_Linear:TLV75510PDRV +Regulator_Linear:TLV75512PDBV +Regulator_Linear:TLV75512PDRV +Regulator_Linear:TLV75515PDBV +Regulator_Linear:TLV75515PDRV +Regulator_Linear:TLV75518PDBV +Regulator_Linear:TLV75518PDRV +Regulator_Linear:TLV75519PDBV +Regulator_Linear:TLV75519PDRV +Regulator_Linear:TLV75525PDBV +Regulator_Linear:TLV75525PDRV +Regulator_Linear:TLV75528PDBV +Regulator_Linear:TLV75528PDRV +Regulator_Linear:TLV75529PDBV +Regulator_Linear:TLV75529PDRV +Regulator_Linear:TLV75530PDBV +Regulator_Linear:TLV75530PDRV +Regulator_Linear:TLV75533PDBV +Regulator_Linear:TLV75533PDRV +Regulator_Linear:TLV75709PDBV +Regulator_Linear:TLV75709PDRV +Regulator_Linear:TLV75710PDBV +Regulator_Linear:TLV75710PDRV +Regulator_Linear:TLV75712PDBV +Regulator_Linear:TLV75712PDRV +Regulator_Linear:TLV75715PDBV +Regulator_Linear:TLV75715PDRV +Regulator_Linear:TLV75718PDBV +Regulator_Linear:TLV75718PDRV +Regulator_Linear:TLV75719PDBV +Regulator_Linear:TLV75719PDRV +Regulator_Linear:TLV75725PDBV +Regulator_Linear:TLV75725PDRV +Regulator_Linear:TLV75728PDBV +Regulator_Linear:TLV75728PDRV +Regulator_Linear:TLV75729PDBV +Regulator_Linear:TLV75730PDBV +Regulator_Linear:TLV75730PDRV +Regulator_Linear:TLV75733PDBV +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 +Regulator_Linear:TLV76718DRVx +Regulator_Linear:TLV76728DRVx +Regulator_Linear:TLV76733DRVx +Regulator_Linear:TLV76733QWDRBxQ1 +Regulator_Linear:TLV76750DRVx +Regulator_Linear:TLV76750QWDRBxQ1 +Regulator_Linear:TLV76760QWDRBxQ1 +Regulator_Linear:TLV76780QWDRBxQ1 +Regulator_Linear:TLV76790QWDRBxQ1 +Regulator_Linear:TPS51200DRC +Regulator_Linear:TPS70202_HTSSOP20 +Regulator_Linear:TPS70245_HTSSOP20 +Regulator_Linear:TPS70248_HTSSOP20 +Regulator_Linear:TPS70251_HTSSOP20 +Regulator_Linear:TPS70258_HTSSOP20 +Regulator_Linear:TPS70302 +Regulator_Linear:TPS70345 +Regulator_Linear:TPS70348 +Regulator_Linear:TPS70351 +Regulator_Linear:TPS70358 +Regulator_Linear:TPS70402 +Regulator_Linear:TPS70445 +Regulator_Linear:TPS70448 +Regulator_Linear:TPS70451 +Regulator_Linear:TPS70458 +Regulator_Linear:TPS7101 +Regulator_Linear:TPS7133 +Regulator_Linear:TPS7148 +Regulator_Linear:TPS7150 +Regulator_Linear:TPS71518__SC70 +Regulator_Linear:TPS71519__SC70 +Regulator_Linear:TPS71523__SC70 +Regulator_Linear:TPS71525__SC70 +Regulator_Linear:TPS71530__SC70 +Regulator_Linear:TPS71533__SC70 +Regulator_Linear:TPS715345__SC70 +Regulator_Linear:TPS71550__SC70 +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 +Regulator_Linear:TPS731125DBV +Regulator_Linear:TPS73115DBV +Regulator_Linear:TPS73118DBV +Regulator_Linear:TPS73125DBV +Regulator_Linear:TPS73130DBV +Regulator_Linear:TPS73131DBV +Regulator_Linear:TPS73132DBV +Regulator_Linear:TPS73133DBV +Regulator_Linear:TPS73150DBV +Regulator_Linear:TPS73601DBV +Regulator_Linear:TPS736125DBV +Regulator_Linear:TPS73615DBV +Regulator_Linear:TPS73616DBV +Regulator_Linear:TPS73618DBV +Regulator_Linear:TPS73619DBV +Regulator_Linear:TPS73625DBV +Regulator_Linear:TPS73630DBV +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 +Regulator_Linear:TPS76318 +Regulator_Linear:TPS76325 +Regulator_Linear:TPS76327 +Regulator_Linear:TPS76329 +Regulator_Linear:TPS76330 +Regulator_Linear:TPS76333 +Regulator_Linear:TPS76338 +Regulator_Linear:TPS76350 +Regulator_Linear:TPS76901 +Regulator_Linear:TPS76912 +Regulator_Linear:TPS76915 +Regulator_Linear:TPS76918 +Regulator_Linear:TPS76925 +Regulator_Linear:TPS76927 +Regulator_Linear:TPS76928 +Regulator_Linear:TPS76930 +Regulator_Linear:TPS76933 +Regulator_Linear:TPS76950 +Regulator_Linear:TPS77701_HTSSOP20 +Regulator_Linear:TPS77701_SO8 +Regulator_Linear:TPS77715_HTSSOP20 +Regulator_Linear:TPS77715_SO8 +Regulator_Linear:TPS77718_HTSSOP20 +Regulator_Linear:TPS77718_SO8 +Regulator_Linear:TPS77725_HTSSOP20 +Regulator_Linear:TPS77725_SO8 +Regulator_Linear:TPS77733_HTSSOP20 +Regulator_Linear:TPS77733_SO8 +Regulator_Linear:TPS77801_HTSSOP20 +Regulator_Linear:TPS77801_SO8 +Regulator_Linear:TPS77815_HTSSOP20 +Regulator_Linear:TPS77815_SO8 +Regulator_Linear:TPS77818_HTSSOP20 +Regulator_Linear:TPS77818_SO8 +Regulator_Linear:TPS77825_HTSSOP20 +Regulator_Linear:TPS77825_SO8 +Regulator_Linear:TPS77833_HTSSOP20 +Regulator_Linear:TPS77833_SO8 +Regulator_Linear:TPS78218DDC +Regulator_Linear:TPS78223DDC +Regulator_Linear:TPS78225DDC +Regulator_Linear:TPS78227DDC +Regulator_Linear:TPS78228DDC +Regulator_Linear:TPS78230DDC +Regulator_Linear:TPS78233DDC +Regulator_Linear:TPS78236DDC +Regulator_Linear:TPS79301-EP +Regulator_Linear:TPS79318-EP +Regulator_Linear:TPS79325-EP +Regulator_Linear:TPS79328-EP +Regulator_Linear:TPS793285-EP +Regulator_Linear:TPS79330-EP +Regulator_Linear:TPS79333-EP +Regulator_Linear:TPS793475-EP +Regulator_Linear:TPS7A0508PDBV +Regulator_Linear:TPS7A0508PDBZ +Regulator_Linear:TPS7A0510PDBV +Regulator_Linear:TPS7A0512PDBV +Regulator_Linear:TPS7A0512PDBZ +Regulator_Linear:TPS7A0515PDBV +Regulator_Linear:TPS7A0518PDBV +Regulator_Linear:TPS7A0518PDBZ +Regulator_Linear:TPS7A0520PDBZ +Regulator_Linear:TPS7A0522PDBV +Regulator_Linear:TPS7A0522PDBZ +Regulator_Linear:TPS7A0525PDBV +Regulator_Linear:TPS7A0527PDBZ +Regulator_Linear:TPS7A05285PDBV +Regulator_Linear:TPS7A0528PDBZ +Regulator_Linear:TPS7A0530PDBV +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 +Regulator_Linear:uA7805 +Regulator_Linear:uA7808 +Regulator_Linear:uA7810 +Regulator_Linear:uA7812 +Regulator_Linear:uA7815 +Regulator_Linear:uA7824 +Regulator_SwitchedCapacitor:CAT3200 +Regulator_SwitchedCapacitor:CAT3200-5 +Regulator_SwitchedCapacitor:ICL7660 +Regulator_SwitchedCapacitor:LM2665M6 +Regulator_SwitchedCapacitor:LM2775DSG +Regulator_SwitchedCapacitor:LM2776 +Regulator_SwitchedCapacitor:LM27761 +Regulator_SwitchedCapacitor:LM27762 +Regulator_SwitchedCapacitor:LM7705 +Regulator_SwitchedCapacitor:LMC7660 +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 +Regulator_SwitchedCapacitor:TPS60503DGS +Regulator_Switching:171050601 +Regulator_Switching:A4403GEU +Regulator_Switching:AAT1217ICA-1.2 +Regulator_Switching:AAT1217ICA-3.3 +Regulator_Switching:AAT1217ICA-5.0 +Regulator_Switching:AAT1217IGU-3.3 +Regulator_Switching:ADP1108AN +Regulator_Switching:ADP1108AN-12 +Regulator_Switching:ADP1108AN-3.3 +Regulator_Switching:ADP1108AN-5 +Regulator_Switching:ADP1108AR +Regulator_Switching:ADP1108AR-12 +Regulator_Switching:ADP1108AR-3.3 +Regulator_Switching:ADP1108AR-5 +Regulator_Switching:ADP2108AUJ-1.0 +Regulator_Switching:ADP2108AUJ-1.1 +Regulator_Switching:ADP2108AUJ-1.2 +Regulator_Switching:ADP2108AUJ-1.3 +Regulator_Switching:ADP2108AUJ-1.5 +Regulator_Switching:ADP2108AUJ-1.8 +Regulator_Switching:ADP2108AUJ-1.82 +Regulator_Switching:ADP2108AUJ-2.3 +Regulator_Switching:ADP2108AUJ-2.5 +Regulator_Switching:ADP2108AUJ-3.0 +Regulator_Switching:ADP2108AUJ-3.3 +Regulator_Switching:ADP2302ARDZ +Regulator_Switching:ADP2302ARDZ-2.5 +Regulator_Switching:ADP2302ARDZ-3.3 +Regulator_Switching:ADP2302ARDZ-5.0 +Regulator_Switching:ADP2303ARDZ +Regulator_Switching:ADP2303ARDZ-2.5 +Regulator_Switching:ADP2303ARDZ-3.3 +Regulator_Switching:ADP2303ARDZ-5.0 +Regulator_Switching:ADP2360xCP +Regulator_Switching:ADP2360xCP-3.3 +Regulator_Switching:ADP2360xCP-5.0 +Regulator_Switching:ADP5054 +Regulator_Switching:ADP5070AREZ +Regulator_Switching:ADP5071AREZ +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 +Regulator_Switching:AP62300Z6 +Regulator_Switching:AP62301WU +Regulator_Switching:AP63200WU +Regulator_Switching:AP63201WU +Regulator_Switching:AP63203WU +Regulator_Switching:AP63205WU +Regulator_Switching:AP6502 +Regulator_Switching:AP6503 +Regulator_Switching:AP65111AWU +Regulator_Switching:APE1707H-12-HF +Regulator_Switching:APE1707H-33-HF +Regulator_Switching:APE1707H-50-HF +Regulator_Switching:APE1707H-HF +Regulator_Switching:APE1707M-12-HF +Regulator_Switching:APE1707M-33-HF +Regulator_Switching:APE1707M-50-HF +Regulator_Switching:APE1707M-HF +Regulator_Switching:APE1707S-12-HF +Regulator_Switching:APE1707S-33-HF +Regulator_Switching:APE1707S-50-HF +Regulator_Switching:APE1707S-HF +Regulator_Switching:BD9001F +Regulator_Switching:BD9778F +Regulator_Switching:BD9778HFP +Regulator_Switching:BD9781HFP +Regulator_Switching:BD9G341EFJ +Regulator_Switching:CRE1S0305S3C +Regulator_Switching:CRE1S0505DC +Regulator_Switching:CRE1S0505S3C +Regulator_Switching:CRE1S0505SC +Regulator_Switching:CRE1S0515SC +Regulator_Switching:CRE1S1205SC +Regulator_Switching:CRE1S1212SC +Regulator_Switching:CRE1S2405SC +Regulator_Switching:CRE1S2412SC +Regulator_Switching:DIO6970 +Regulator_Switching:FSBH0170 +Regulator_Switching:FSBH0170A +Regulator_Switching:FSBH0170W +Regulator_Switching:FSBH0270 +Regulator_Switching:FSBH0270A +Regulator_Switching:FSBH0270W +Regulator_Switching:FSBH0370 +Regulator_Switching:FSBH0F70A +Regulator_Switching:FSBH0F70WA +Regulator_Switching:FSDH321 +Regulator_Switching:FSDH321L +Regulator_Switching:FSDL321 +Regulator_Switching:FSDL321L +Regulator_Switching:FSL136MRT +Regulator_Switching:FSQ0565RQLDTU +Regulator_Switching:FSQ0565RQWDTU +Regulator_Switching:FSQ0565RSLDTU +Regulator_Switching:FSQ0565RSWDTU +Regulator_Switching:GL2576-12SF8DR +Regulator_Switching:GL2576-12TA5R +Regulator_Switching:GL2576-12TB5T +Regulator_Switching:GL2576-15SF8DR +Regulator_Switching:GL2576-15TA5R +Regulator_Switching:GL2576-15TB5T +Regulator_Switching:GL2576-3.3SF8DR +Regulator_Switching:GL2576-3.3TA5R +Regulator_Switching:GL2576-3.3TB5T +Regulator_Switching:GL2576-5.0SF8DR +Regulator_Switching:GL2576-5.0TA5R +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 +Regulator_Switching:KA5H0265RCTU +Regulator_Switching:KA5H0265RCYDTU +Regulator_Switching:KA5H0280RTU +Regulator_Switching:KA5H0280RYDTU +Regulator_Switching:KA5L0265RTU +Regulator_Switching:KA5L0265RYDTU +Regulator_Switching:KA5M02659RN +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:LGS5116B +Regulator_Switching:LGS5145 +Regulator_Switching:LGS6302B5 +Regulator_Switching:LM22676MR-5 +Regulator_Switching:LM22676MR-ADJ +Regulator_Switching:LM22678TJ-5 +Regulator_Switching:LM22678TJ-ADJ +Regulator_Switching:LM25085MM +Regulator_Switching:LM25085MY +Regulator_Switching:LM25085SD +Regulator_Switching:LM2574HVM-12 +Regulator_Switching:LM2574HVM-15 +Regulator_Switching:LM2574HVM-3.3 +Regulator_Switching:LM2574HVM-5 +Regulator_Switching:LM2574HVM-ADJ +Regulator_Switching:LM2574HVN-12 +Regulator_Switching:LM2574HVN-15 +Regulator_Switching:LM2574HVN-3.3 +Regulator_Switching:LM2574HVN-5 +Regulator_Switching:LM2574HVN-ADJ +Regulator_Switching:LM2574M-12 +Regulator_Switching:LM2574M-15 +Regulator_Switching:LM2574M-3.3 +Regulator_Switching:LM2574M-5 +Regulator_Switching:LM2574M-ADJ +Regulator_Switching:LM2574N-12 +Regulator_Switching:LM2574N-15 +Regulator_Switching:LM2574N-3.3 +Regulator_Switching:LM2574N-5 +Regulator_Switching:LM2574N-ADJ +Regulator_Switching:LM2575-12BT +Regulator_Switching:LM2575-12BU +Regulator_Switching:LM2575-3.3BT +Regulator_Switching:LM2575-3.3BU +Regulator_Switching:LM2575-5.0BT +Regulator_Switching:LM2575-5.0BU +Regulator_Switching:LM2575BT-ADJ +Regulator_Switching:LM2575BU-ADJ +Regulator_Switching:LM2576HVS-12 +Regulator_Switching:LM2576HVS-15 +Regulator_Switching:LM2576HVS-3.3 +Regulator_Switching:LM2576HVS-5 +Regulator_Switching:LM2576HVS-ADJ +Regulator_Switching:LM2576HVT-12 +Regulator_Switching:LM2576HVT-15 +Regulator_Switching:LM2576HVT-3.3 +Regulator_Switching:LM2576HVT-5 +Regulator_Switching:LM2576HVT-ADJ +Regulator_Switching:LM2576S-12 +Regulator_Switching:LM2576S-15 +Regulator_Switching:LM2576S-3.3 +Regulator_Switching:LM2576S-5 +Regulator_Switching:LM2576S-ADJ +Regulator_Switching:LM2576T-12 +Regulator_Switching:LM2576T-15 +Regulator_Switching:LM2576T-3.3 +Regulator_Switching:LM2576T-5 +Regulator_Switching:LM2576T-ADJ +Regulator_Switching:LM2578 +Regulator_Switching:LM2594HVM-12 +Regulator_Switching:LM2594HVM-3.3 +Regulator_Switching:LM2594HVM-5.0 +Regulator_Switching:LM2594HVM-ADJ +Regulator_Switching:LM2594HVN-12 +Regulator_Switching:LM2594HVN-3.3 +Regulator_Switching:LM2594HVN-5.0 +Regulator_Switching:LM2594HVN-ADJ +Regulator_Switching:LM2594M-12 +Regulator_Switching:LM2594M-3.3 +Regulator_Switching:LM2594M-5.0 +Regulator_Switching:LM2594M-ADJ +Regulator_Switching:LM2594N-12 +Regulator_Switching:LM2594N-3.3 +Regulator_Switching:LM2594N-5.0 +Regulator_Switching:LM2594N-ADJ +Regulator_Switching:LM2595S-12 +Regulator_Switching:LM2595S-3.3 +Regulator_Switching:LM2595S-5 +Regulator_Switching:LM2595S-ADJ +Regulator_Switching:LM2595T-12 +Regulator_Switching:LM2595T-3.3 +Regulator_Switching:LM2595T-5 +Regulator_Switching:LM2595T-ADJ +Regulator_Switching:LM2596S-12 +Regulator_Switching:LM2596S-3.3 +Regulator_Switching:LM2596S-5 +Regulator_Switching:LM2596S-ADJ +Regulator_Switching:LM2596T-12 +Regulator_Switching:LM2596T-3.3 +Regulator_Switching:LM2596T-5 +Regulator_Switching:LM2596T-ADJ +Regulator_Switching:LM2611xMF +Regulator_Switching:LM26480SQ +Regulator_Switching:LM2672M-12 +Regulator_Switching:LM2672M-3.3 +Regulator_Switching:LM2672M-5.0 +Regulator_Switching:LM2672M-ADJ +Regulator_Switching:LM2672N-12 +Regulator_Switching:LM2672N-3.3 +Regulator_Switching:LM2672N-5.0 +Regulator_Switching:LM2672N-ADJ +Regulator_Switching:LM2674M-12 +Regulator_Switching:LM2674M-3.3 +Regulator_Switching:LM2674M-5.0 +Regulator_Switching:LM2674M-ADJ +Regulator_Switching:LM2674N-12 +Regulator_Switching:LM2674N-3.3 +Regulator_Switching:LM2674N-5.0 +Regulator_Switching:LM2674N-ADJ +Regulator_Switching:LM2675M-12 +Regulator_Switching:LM2675M-3.3 +Regulator_Switching:LM2675M-5 +Regulator_Switching:LM2675M-ADJ +Regulator_Switching:LM2675N-12 +Regulator_Switching:LM2675N-3.3 +Regulator_Switching:LM2675N-5 +Regulator_Switching:LM2675N-ADJ +Regulator_Switching:LM27313XMF +Regulator_Switching:LM2731XMF +Regulator_Switching:LM2731YMF +Regulator_Switching:LM2733XMF +Regulator_Switching:LM2733YMF +Regulator_Switching:LM2734X +Regulator_Switching:LM2734Y +Regulator_Switching:LM2735XMF +Regulator_Switching:LM2840X +Regulator_Switching:LM2840Y +Regulator_Switching:LM2841X +Regulator_Switching:LM2841Y +Regulator_Switching:LM2842X +Regulator_Switching:LM2842Y +Regulator_Switching:LM3150MH +Regulator_Switching:LM3407MY +Regulator_Switching:LM3578 +Regulator_Switching:LM3670MF +Regulator_Switching:LM5001MA +Regulator_Switching:LM5006MM +Regulator_Switching:LM5007MM +Regulator_Switching:LM5007SD +Regulator_Switching:LM5008MM +Regulator_Switching:LM5008SD +Regulator_Switching:LM5009MM +Regulator_Switching:LM5009SD +Regulator_Switching:LM5017MR +Regulator_Switching:LM5017SD +Regulator_Switching:LM5022MM +Regulator_Switching:LM5088-1 +Regulator_Switching:LM5088-2 +Regulator_Switching:LM5118MH +Regulator_Switching:LM5161PWP +Regulator_Switching:LM5164DDA +Regulator_Switching:LM5165 +Regulator_Switching:LM5165X +Regulator_Switching:LM5165Y +Regulator_Switching:LM5166 +Regulator_Switching:LM5166X +Regulator_Switching:LM5166Y +Regulator_Switching:LM5175PWP +Regulator_Switching:LM5175RHF +Regulator_Switching:LM5176PWP +Regulator_Switching:LM5176RHF +Regulator_Switching:LMR10510XMF +Regulator_Switching:LMR10510YMF +Regulator_Switching:LMR10510YSD +Regulator_Switching:LMR14206 +Regulator_Switching:LMR16006YQ +Regulator_Switching:LMR16006YQ3 +Regulator_Switching:LMR16006YQ5 +Regulator_Switching:LMR33610ADDAR +Regulator_Switching:LMR33610BDDAR +Regulator_Switching:LMR33620ADDA +Regulator_Switching:LMR33620BDDA +Regulator_Switching:LMR33620CDDA +Regulator_Switching:LMR33630ADDA +Regulator_Switching:LMR33630BDDA +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 +Regulator_Switching:LMR64010XMF +Regulator_Switching:LMZ13608 +Regulator_Switching:LMZ22003TZ +Regulator_Switching:LMZ22005TZ +Regulator_Switching:LMZ23603TZ +Regulator_Switching:LMZ23605TZ +Regulator_Switching:LMZM23600 +Regulator_Switching:LMZM23600V3 +Regulator_Switching:LMZM23600V5 +Regulator_Switching:LMZM23601 +Regulator_Switching:LMZM23601V3 +Regulator_Switching:LMZM23601V5 +Regulator_Switching:LNK302D +Regulator_Switching:LNK302G +Regulator_Switching:LNK302P +Regulator_Switching:LNK304D +Regulator_Switching:LNK304G +Regulator_Switching:LNK304P +Regulator_Switching:LNK305D +Regulator_Switching:LNK305G +Regulator_Switching:LNK305P +Regulator_Switching:LNK306D +Regulator_Switching:LNK306G +Regulator_Switching:LNK306P +Regulator_Switching:LNK3202D +Regulator_Switching:LNK3202G +Regulator_Switching:LNK3202P +Regulator_Switching:LNK3204D +Regulator_Switching:LNK3204G +Regulator_Switching:LNK3204P +Regulator_Switching:LNK3205D +Regulator_Switching:LNK3205G +Regulator_Switching:LNK3205P +Regulator_Switching:LNK3206D +Regulator_Switching:LNK3206G +Regulator_Switching:LNK3206P +Regulator_Switching:LNK362D +Regulator_Switching:LNK362G +Regulator_Switching:LNK362P +Regulator_Switching:LNK363D +Regulator_Switching:LNK363G +Regulator_Switching:LNK363P +Regulator_Switching:LNK364D +Regulator_Switching:LNK364G +Regulator_Switching:LNK364P +Regulator_Switching:LNK403EG +Regulator_Switching:LNK403LG +Regulator_Switching:LNK404EG +Regulator_Switching:LNK404LG +Regulator_Switching:LNK405EG +Regulator_Switching:LNK405LG +Regulator_Switching:LNK406EG +Regulator_Switching:LNK406LG +Regulator_Switching:LNK407EG +Regulator_Switching:LNK407LG +Regulator_Switching:LNK408EG +Regulator_Switching:LNK408LG +Regulator_Switching:LNK409EG +Regulator_Switching:LNK409LG +Regulator_Switching:LNK410EG +Regulator_Switching:LNK410LG +Regulator_Switching:LNK413EG +Regulator_Switching:LNK413LG +Regulator_Switching:LNK414EG +Regulator_Switching:LNK414LG +Regulator_Switching:LNK415EG +Regulator_Switching:LNK415LG +Regulator_Switching:LNK416EG +Regulator_Switching:LNK416LG +Regulator_Switching:LNK417EG +Regulator_Switching:LNK417LG +Regulator_Switching:LNK418EG +Regulator_Switching:LNK418LG +Regulator_Switching:LNK419EG +Regulator_Switching:LNK419LG +Regulator_Switching:LNK420EG +Regulator_Switching:LNK420LG +Regulator_Switching:LNK454D +Regulator_Switching:LNK456D +Regulator_Switching:LNK457D +Regulator_Switching:LNK457K +Regulator_Switching:LNK457V +Regulator_Switching:LNK458K +Regulator_Switching:LNK458V +Regulator_Switching:LNK460K +Regulator_Switching:LNK460V +Regulator_Switching:LNK562D +Regulator_Switching:LNK562G +Regulator_Switching:LNK562P +Regulator_Switching:LNK563D +Regulator_Switching:LNK563G +Regulator_Switching:LNK563P +Regulator_Switching:LNK564D +Regulator_Switching:LNK564G +Regulator_Switching:LNK564P +Regulator_Switching:LNK603DG +Regulator_Switching:LNK603PG +Regulator_Switching:LNK604DG +Regulator_Switching:LNK604PG +Regulator_Switching:LNK605DG +Regulator_Switching:LNK605PG +Regulator_Switching:LNK606DG +Regulator_Switching:LNK606GG +Regulator_Switching:LNK606PG +Regulator_Switching:LNK613DG +Regulator_Switching:LNK613PG +Regulator_Switching:LNK614DG +Regulator_Switching:LNK614PG +Regulator_Switching:LNK615DG +Regulator_Switching:LNK615PG +Regulator_Switching:LNK616DG +Regulator_Switching:LNK616GG +Regulator_Switching:LNK616PG +Regulator_Switching:LNK623DG +Regulator_Switching:LNK623PG +Regulator_Switching:LNK624DG +Regulator_Switching:LNK624PG +Regulator_Switching:LNK625DG +Regulator_Switching:LNK625PG +Regulator_Switching:LNK626DG +Regulator_Switching:LNK626PG +Regulator_Switching:LNK632DG +Regulator_Switching:LT1073CN +Regulator_Switching:LT1073CN-12 +Regulator_Switching:LT1073CN-5 +Regulator_Switching:LT1073CS +Regulator_Switching:LT1073CS-12 +Regulator_Switching:LT1073CS-5 +Regulator_Switching:LT1108CN +Regulator_Switching:LT1108CN-12 +Regulator_Switching:LT1108CN-5 +Regulator_Switching:LT1108CS +Regulator_Switching:LT1108CS-12 +Regulator_Switching:LT1108CS-5 +Regulator_Switching:LT1301 +Regulator_Switching:LT1307BCMS8 +Regulator_Switching:LT1307BCS8 +Regulator_Switching:LT1307CMS8 +Regulator_Switching:LT1307CN8 +Regulator_Switching:LT1307CS8 +Regulator_Switching:LT1372CN8 +Regulator_Switching:LT1372CS8 +Regulator_Switching:LT1372HVCN8 +Regulator_Switching:LT1372HVCS8 +Regulator_Switching:LT1373CN8 +Regulator_Switching:LT1373CS8 +Regulator_Switching:LT1373HVCN8 +Regulator_Switching:LT1373HVCS8 +Regulator_Switching:LT1377CN8 +Regulator_Switching:LT1377CS8 +Regulator_Switching:LT1945 +Regulator_Switching:LT3430 +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 +Regulator_Switching:LT3748xMS +Regulator_Switching:LT3757AEDD +Regulator_Switching:LT3757AEMSE +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 +Regulator_Switching:LTC3406ES5 +Regulator_Switching:LTC3406ES5-1.2 +Regulator_Switching:LTC3406ES5-1.5 +Regulator_Switching:LTC3406ES5-1.8 +Regulator_Switching:LTC3429 +Regulator_Switching:LTC3429B +Regulator_Switching:LTC3442 +Regulator_Switching:LTC3525 +Regulator_Switching:LTC3525-3 +Regulator_Switching:LTC3525-3.3 +Regulator_Switching:LTC3525-5 +Regulator_Switching:LTC3525D-3.3 +Regulator_Switching:LTC3525L-3 +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: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 +Regulator_Switching:MAX17501FxTB +Regulator_Switching:MAX17501GxTB +Regulator_Switching:MAX17501HxTB +Regulator_Switching:MAX17572 +Regulator_Switching:MAX17574 +Regulator_Switching:MAX17620ATA +Regulator_Switching:MAX1771xSA +Regulator_Switching:MAX5035AUPA +Regulator_Switching:MAX5035AUSA +Regulator_Switching:MAX5035BUPA +Regulator_Switching:MAX5035BUSA +Regulator_Switching:MAX5035CUPA +Regulator_Switching:MAX5035CUSA +Regulator_Switching:MAX5035DUPA +Regulator_Switching:MAX5035DUSA +Regulator_Switching:MAX5035EUSA +Regulator_Switching:MAX777L +Regulator_Switching:MAX77827AEFD +Regulator_Switching:MAX778L +Regulator_Switching:MAX779L +Regulator_Switching:MC33063AD +Regulator_Switching:MC33063AP +Regulator_Switching:MC33063MNTXG +Regulator_Switching:MC34063AD +Regulator_Switching:MC34063AP +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 +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 +Regulator_Switching:MIC23050-GYML +Regulator_Switching:MIC23050-SYML +Regulator_Switching:MIC4684 +Regulator_Switching:MIC4690 +Regulator_Switching:MP1470 +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 +Regulator_Switching:NBM5100A +Regulator_Switching:NBM7100A +Regulator_Switching:NCP1070P065 +Regulator_Switching:NCP1070P100 +Regulator_Switching:NCP1070P130 +Regulator_Switching:NCP1070STAT +Regulator_Switching:NCP1070STBT +Regulator_Switching:NCP1070STCT +Regulator_Switching:NCP1071P065 +Regulator_Switching:NCP1071P100 +Regulator_Switching:NCP1071P130 +Regulator_Switching:NCP1071STAT +Regulator_Switching:NCP1071STBT +Regulator_Switching:NCP1071STCT +Regulator_Switching:NCP1072P065 +Regulator_Switching:NCP1072P100 +Regulator_Switching:NCP1072P130 +Regulator_Switching:NCP1072STAT +Regulator_Switching:NCP1072STBT +Regulator_Switching:NCP1072STCT +Regulator_Switching:NCP1075P065 +Regulator_Switching:NCP1075P100 +Regulator_Switching:NCP1075P130 +Regulator_Switching:NCP1075STAT +Regulator_Switching:NCP1075STBT +Regulator_Switching:NCP1075STCT +Regulator_Switching:NCP1076P065 +Regulator_Switching:NCP1076P100 +Regulator_Switching:NCP1076P130 +Regulator_Switching:NCP1076STAT +Regulator_Switching:NCP1076STBT +Regulator_Switching:NCP1076STCT +Regulator_Switching:NCP1077P065 +Regulator_Switching:NCP1077P100 +Regulator_Switching:NCP1077P130 +Regulator_Switching:NCP1077STAT +Regulator_Switching:NCP1077STBT +Regulator_Switching:NCP1077STCT +Regulator_Switching:NCP1529A +Regulator_Switching:NCV33063AVD +Regulator_Switching:NID30S24-05 +Regulator_Switching:NID30S24-12 +Regulator_Switching:NID30S24-15 +Regulator_Switching:NID30S48-24 +Regulator_Switching:NID60S24-05 +Regulator_Switching:NID60S24-12 +Regulator_Switching:NID60S24-15 +Regulator_Switching:NID60S48-24 +Regulator_Switching:NMA0505DC +Regulator_Switching:NMA0505SC +Regulator_Switching:NMA0509DC +Regulator_Switching:NMA0509SC +Regulator_Switching:NMA0512DC +Regulator_Switching:NMA0512SC +Regulator_Switching:NMA0515DC +Regulator_Switching:NMA0515SC +Regulator_Switching:NMA1205DC +Regulator_Switching:NMA1205SC +Regulator_Switching:NMA1209DC +Regulator_Switching:NMA1209SC +Regulator_Switching:NMA1212DC +Regulator_Switching:NMA1212SC +Regulator_Switching:NMA1215DC +Regulator_Switching:NMA1215SC +Regulator_Switching:NMA1505DC +Regulator_Switching:NMA1505SC +Regulator_Switching:NMA1512DC +Regulator_Switching:NMA1512SC +Regulator_Switching:NMA1515DC +Regulator_Switching:NMA1515SC +Regulator_Switching:NXE1S0303MC +Regulator_Switching:NXE1S0305MC +Regulator_Switching:NXE1S0505MC +Regulator_Switching:NXE2S0505MC +Regulator_Switching:NXE2S1205MC +Regulator_Switching:NXE2S1212MC +Regulator_Switching:NXE2S1215MC +Regulator_Switching:PAM2301CAAB120 +Regulator_Switching:PAM2301CAAB330 +Regulator_Switching:PAM2301CAABADJ +Regulator_Switching:PAM2305AAB120 +Regulator_Switching:PAM2305AAB150 +Regulator_Switching:PAM2305AAB180 +Regulator_Switching:PAM2305AAB250 +Regulator_Switching:PAM2305AAB280 +Regulator_Switching:PAM2305AAB330 +Regulator_Switching:PAM2305AABADJ +Regulator_Switching:PAM2305BJE120 +Regulator_Switching:PAM2305BJE150 +Regulator_Switching:PAM2305BJE180 +Regulator_Switching:PAM2305BJE250 +Regulator_Switching:PAM2305BJE280 +Regulator_Switching:PAM2305BJE330 +Regulator_Switching:PAM2305BJEADJ +Regulator_Switching:PAM2305CGF120 +Regulator_Switching:PAM2305CGF150 +Regulator_Switching:PAM2305CGF180 +Regulator_Switching:PAM2305CGF250 +Regulator_Switching:PAM2305CGF280 +Regulator_Switching:PAM2305CGF330 +Regulator_Switching:PAM2305CGFADJ +Regulator_Switching:PAM2306AYPAA +Regulator_Switching:PAM2306AYPBB +Regulator_Switching:PAM2306AYPBK +Regulator_Switching:PAM2306AYPCB +Regulator_Switching:PAM2306AYPKB +Regulator_Switching:PAM2306AYPKE +Regulator_Switching:PAM2306DYPAA +Regulator_Switching:R-781.5-0.5 +Regulator_Switching:R-781.8-0.5 +Regulator_Switching:R-781.8-1.0 +Regulator_Switching:R-7812-0.5 +Regulator_Switching:R-7815-0.5 +Regulator_Switching:R-782.5-0.5 +Regulator_Switching:R-782.5-1.0 +Regulator_Switching:R-783.3-0.5 +Regulator_Switching:R-783.3-1.0 +Regulator_Switching:R-785.0-0.5 +Regulator_Switching:R-785.0-1.0 +Regulator_Switching:R-786.5-0.5 +Regulator_Switching:R-78B1.2-2.0 +Regulator_Switching:R-78B1.5-2.0 +Regulator_Switching:R-78B1.8-2.0 +Regulator_Switching:R-78B12-2.0 +Regulator_Switching:R-78B15-2.0 +Regulator_Switching:R-78B2.5-2.0 +Regulator_Switching:R-78B3.3-2.0 +Regulator_Switching:R-78B5.0-2.0 +Regulator_Switching:R-78B9.0-2.0 +Regulator_Switching:R-78C1.8-1.0 +Regulator_Switching:R-78C12-1.0 +Regulator_Switching:R-78C15-1.0 +Regulator_Switching:R-78C3.3-1.0 +Regulator_Switching:R-78C5.0-1.0 +Regulator_Switching:R-78C9.0-1.0 +Regulator_Switching:R-78E12-0.5 +Regulator_Switching:R-78E15-0.5 +Regulator_Switching:R-78E3.3-0.5 +Regulator_Switching:R-78E3.3-1.0 +Regulator_Switching:R-78E5.0-0.5 +Regulator_Switching:R-78E5.0-1.0 +Regulator_Switching:R-78E9.0-0.5 +Regulator_Switching:R-78HB12-0.5 +Regulator_Switching:R-78HB15-0.5 +Regulator_Switching:R-78HB24-0.3 +Regulator_Switching:R-78HB3.3-0.5 +Regulator_Switching:R-78HB5.0-0.5 +Regulator_Switching:R-78HB6.5-0.5 +Regulator_Switching:R-78HB9.0-0.5 +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 +Regulator_Switching:ST1S14PHR +Regulator_Switching:TDN_5-0910WISM +Regulator_Switching:TDN_5-0911WISM +Regulator_Switching:TDN_5-0912WISM +Regulator_Switching:TDN_5-0913WISM +Regulator_Switching:TDN_5-0915WISM +Regulator_Switching:TDN_5-0919WISM +Regulator_Switching:TDN_5-2410WISM +Regulator_Switching:TDN_5-2411WISM +Regulator_Switching:TDN_5-2412WISM +Regulator_Switching:TDN_5-2413WISM +Regulator_Switching:TDN_5-2415WISM +Regulator_Switching:TDN_5-2419WISM +Regulator_Switching:TDN_5-4810WISM +Regulator_Switching:TDN_5-4811WISM +Regulator_Switching:TDN_5-4812WISM +Regulator_Switching:TDN_5-4813WISM +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 +Regulator_Switching:TMR_1-0511 +Regulator_Switching:TMR_1-0511SM +Regulator_Switching:TMR_1-0512 +Regulator_Switching:TMR_1-0512SM +Regulator_Switching:TMR_1-0513 +Regulator_Switching:TMR_1-0513SM +Regulator_Switching:TMR_1-0515 +Regulator_Switching:TMR_1-0522 +Regulator_Switching:TMR_1-0522SM +Regulator_Switching:TMR_1-0523 +Regulator_Switching:TMR_1-0523SM +Regulator_Switching:TMR_1-1211 +Regulator_Switching:TMR_1-1211SM +Regulator_Switching:TMR_1-1212 +Regulator_Switching:TMR_1-1212SM +Regulator_Switching:TMR_1-1213 +Regulator_Switching:TMR_1-1213SM +Regulator_Switching:TMR_1-1215 +Regulator_Switching:TMR_1-1222 +Regulator_Switching:TMR_1-1222SM +Regulator_Switching:TMR_1-1223 +Regulator_Switching:TMR_1-1223SM +Regulator_Switching:TMR_1-2411 +Regulator_Switching:TMR_1-2411SM +Regulator_Switching:TMR_1-2412 +Regulator_Switching:TMR_1-2412SM +Regulator_Switching:TMR_1-2413 +Regulator_Switching:TMR_1-2413SM +Regulator_Switching:TMR_1-2415 +Regulator_Switching:TMR_1-2422 +Regulator_Switching:TMR_1-2422SM +Regulator_Switching:TMR_1-2423 +Regulator_Switching:TMR_1-2423SM +Regulator_Switching:TMR_1-4811 +Regulator_Switching:TMR_1-4811SM +Regulator_Switching:TMR_1-4812 +Regulator_Switching:TMR_1-4812SM +Regulator_Switching:TMR_1-4813 +Regulator_Switching:TMR_1-4813SM +Regulator_Switching:TMR_1-4815 +Regulator_Switching:TMR_1-4822 +Regulator_Switching:TMR_1-4822SM +Regulator_Switching:TMR_1-4823 +Regulator_Switching:TMR_1-4823SM +Regulator_Switching:TNY263G +Regulator_Switching:TNY263P +Regulator_Switching:TNY264G +Regulator_Switching:TNY264P +Regulator_Switching:TNY265G +Regulator_Switching:TNY265P +Regulator_Switching:TNY266G +Regulator_Switching:TNY266P +Regulator_Switching:TNY267G +Regulator_Switching:TNY267P +Regulator_Switching:TNY268G +Regulator_Switching:TNY268P +Regulator_Switching:TNY274G +Regulator_Switching:TNY274P +Regulator_Switching:TNY275G +Regulator_Switching:TNY275P +Regulator_Switching:TNY276G +Regulator_Switching:TNY276P +Regulator_Switching:TNY277G +Regulator_Switching:TNY277P +Regulator_Switching:TNY278G +Regulator_Switching:TNY278P +Regulator_Switching:TNY279G +Regulator_Switching:TNY279P +Regulator_Switching:TNY280G +Regulator_Switching:TNY280P +Regulator_Switching:TNY284D +Regulator_Switching:TNY284K +Regulator_Switching:TNY284P +Regulator_Switching:TNY285D +Regulator_Switching:TNY285K +Regulator_Switching:TNY285P +Regulator_Switching:TNY286D +Regulator_Switching:TNY286K +Regulator_Switching:TNY286P +Regulator_Switching:TNY287D +Regulator_Switching:TNY287K +Regulator_Switching:TNY287P +Regulator_Switching:TNY288D +Regulator_Switching:TNY288K +Regulator_Switching:TNY288P +Regulator_Switching:TNY289K +Regulator_Switching:TNY289P +Regulator_Switching:TNY290K +Regulator_Switching:TNY290P +Regulator_Switching:TOP100YN +Regulator_Switching:TOP101YN +Regulator_Switching:TOP102YN +Regulator_Switching:TOP103YN +Regulator_Switching:TOP104YN +Regulator_Switching:TOP200YAI +Regulator_Switching:TOP201YAI +Regulator_Switching:TOP202YAI +Regulator_Switching:TOP203YAI +Regulator_Switching:TOP204YAI +Regulator_Switching:TOP209G +Regulator_Switching:TOP209P +Regulator_Switching:TOP210G +Regulator_Switching:TOP210PFI +Regulator_Switching:TOP214YAI +Regulator_Switching:TOP252EG +Regulator_Switching:TOP252EN +Regulator_Switching:TOP252GN +Regulator_Switching:TOP252MN +Regulator_Switching:TOP252PN +Regulator_Switching:TOP253EG +Regulator_Switching:TOP253EN +Regulator_Switching:TOP253GN +Regulator_Switching:TOP253MN +Regulator_Switching:TOP253PN +Regulator_Switching:TOP254EG +Regulator_Switching:TOP254EN +Regulator_Switching:TOP254GN +Regulator_Switching:TOP254MN +Regulator_Switching:TOP254PN +Regulator_Switching:TOP254YN +Regulator_Switching:TOP255EG +Regulator_Switching:TOP255EN +Regulator_Switching:TOP255GN +Regulator_Switching:TOP255LN +Regulator_Switching:TOP255MN +Regulator_Switching:TOP255PN +Regulator_Switching:TOP255YN +Regulator_Switching:TOP256EG +Regulator_Switching:TOP256EN +Regulator_Switching:TOP256GN +Regulator_Switching:TOP256LN +Regulator_Switching:TOP256MN +Regulator_Switching:TOP256PN +Regulator_Switching:TOP256YN +Regulator_Switching:TOP257EG +Regulator_Switching:TOP257EN +Regulator_Switching:TOP257GN +Regulator_Switching:TOP257LN +Regulator_Switching:TOP257MN +Regulator_Switching:TOP257PN +Regulator_Switching:TOP257YN +Regulator_Switching:TOP258EG +Regulator_Switching:TOP258EN +Regulator_Switching:TOP258GN +Regulator_Switching:TOP258LN +Regulator_Switching:TOP258MN +Regulator_Switching:TOP258PN +Regulator_Switching:TOP258YN +Regulator_Switching:TOP259EG +Regulator_Switching:TOP259EN +Regulator_Switching:TOP259LN +Regulator_Switching:TOP259YN +Regulator_Switching:TOP260EG +Regulator_Switching:TOP260EN +Regulator_Switching:TOP260LN +Regulator_Switching:TOP260YN +Regulator_Switching:TOP261EG +Regulator_Switching:TOP261EN +Regulator_Switching:TOP261LN +Regulator_Switching:TOP261YN +Regulator_Switching:TOP262EN +Regulator_Switching:TOP262LN +Regulator_Switching:TOP264EG +Regulator_Switching:TOP264KG +Regulator_Switching:TOP264VG +Regulator_Switching:TOP265EG +Regulator_Switching:TOP265KG +Regulator_Switching:TOP265VG +Regulator_Switching:TOP266EG +Regulator_Switching:TOP266KG +Regulator_Switching:TOP266VG +Regulator_Switching:TOP267EG +Regulator_Switching:TOP267KG +Regulator_Switching:TOP267VG +Regulator_Switching:TOP268EG +Regulator_Switching:TOP268KG +Regulator_Switching:TOP268VG +Regulator_Switching:TOP269EG +Regulator_Switching:TOP269KG +Regulator_Switching:TOP269VG +Regulator_Switching:TOP270EG +Regulator_Switching:TOP270KG +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 +Regulator_Switching:TPS5431DDA +Regulator_Switching:TPS54336ADDA +Regulator_Switching:TPS54340DDA +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 +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 +Regulator_Switching:TPS610991DRV +Regulator_Switching:TPS610992DRV +Regulator_Switching:TPS610993DRV +Regulator_Switching:TPS610994DRV +Regulator_Switching:TPS610995DRV +Regulator_Switching:TPS610996DRV +Regulator_Switching:TPS610997DRV +Regulator_Switching:TPS61099DRV +Regulator_Switching:TPS61200DRC +Regulator_Switching:TPS61201DRC +Regulator_Switching:TPS61202DRC +Regulator_Switching:TPS61202DSC +Regulator_Switching:TPS61220DCK +Regulator_Switching:TPS61221DCK +Regulator_Switching:TPS61222DCK +Regulator_Switching:TPS61230DRC +Regulator_Switching:TPS61252DSG +Regulator_Switching:TPS613221ADBV +Regulator_Switching:TPS613221ADBZ +Regulator_Switching:TPS613222ADBV +Regulator_Switching:TPS613222ADBZ +Regulator_Switching:TPS613223ADBV +Regulator_Switching:TPS613223ADBZ +Regulator_Switching:TPS613224ADBV +Regulator_Switching:TPS613224ADBZ +Regulator_Switching:TPS613225ADBV +Regulator_Switching:TPS613225ADBZ +Regulator_Switching:TPS613226ADBV +Regulator_Switching:TPS613226ADBZ +Regulator_Switching:TPS61322DBZ +Regulator_Switching:TPS62056DGS +Regulator_Switching:TPS62125DSG +Regulator_Switching:TPS62130 +Regulator_Switching:TPS62130A +Regulator_Switching:TPS62131 +Regulator_Switching:TPS62132 +Regulator_Switching:TPS62133 +Regulator_Switching:TPS62140 +Regulator_Switching:TPS62140A +Regulator_Switching:TPS62141 +Regulator_Switching:TPS62142 +Regulator_Switching:TPS62143 +Regulator_Switching:TPS62150 +Regulator_Switching:TPS62150A +Regulator_Switching:TPS62151 +Regulator_Switching:TPS62152 +Regulator_Switching:TPS62153 +Regulator_Switching:TPS62160DGK +Regulator_Switching:TPS62160DSG +Regulator_Switching:TPS62161DSG +Regulator_Switching:TPS62162DSG +Regulator_Switching:TPS62163DSG +Regulator_Switching:TPS62170DSG +Regulator_Switching:TPS62171DSG +Regulator_Switching:TPS62172DSG +Regulator_Switching:TPS62173DSG +Regulator_Switching:TPS62175DQC +Regulator_Switching:TPS62177DQC +Regulator_Switching:TPS62200DBV +Regulator_Switching:TPS62201DBV +Regulator_Switching:TPS62202DBV +Regulator_Switching:TPS62203DBV +Regulator_Switching:TPS62204DBV +Regulator_Switching:TPS62205DBV +Regulator_Switching:TPS62207DBV +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 +Regulator_Switching:TPS63002 +Regulator_Switching:TPS63030DSK +Regulator_Switching:TPS63031DSK +Regulator_Switching:TPS63060 +Regulator_Switching:TPS63061 +Regulator_Switching:TPS63900 +Regulator_Switching:TPS65130RGE +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 +Regulator_Switching:TSR_1-24150 +Regulator_Switching:TSR_1-2418 +Regulator_Switching:TSR_1-2425 +Regulator_Switching:TSR_1-2433 +Regulator_Switching:TSR_1-2450 +Regulator_Switching:TSR_1-2465 +Regulator_Switching:TSR_1-2490 +Regulator_Switching:VIPer22ADIP-E +Regulator_Switching:VIPer22AS +Regulator_Switching:VIPer25HN +Regulator_Switching:VIPer25LN +Regulator_Switching:VIPer26HD +Regulator_Switching:VIPer26HN +Regulator_Switching:VIPer26LD +Regulator_Switching:VIPer26LN +Regulator_Switching:XL1509-12 +Regulator_Switching:XL1509-3.3 +Regulator_Switching:XL1509-5.0 +Regulator_Switching:XL1509-ADJ +Regulator_Switching:XL4015 +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 +Relay:EC2-24NU +Relay:EC2-24SNU +Relay:EC2-24TNU +Relay:EC2-3NU +Relay:EC2-3SNU +Relay:EC2-3TNU +Relay:EC2-4.5NU +Relay:EC2-4.5SNU +Relay:EC2-4.5TNU +Relay:EC2-5NU +Relay:EC2-5SNU +Relay:EC2-5TNU +Relay:EE2-12NKX +Relay:EE2-12NU +Relay:EE2-12NUH +Relay:EE2-12NUX +Relay:EE2-12SNU +Relay:EE2-12SNUH +Relay:EE2-12SNUX +Relay:EE2-12TNU +Relay:EE2-12TNUH +Relay:EE2-12TNUX +Relay:EE2-24NU +Relay:EE2-24NUH +Relay:EE2-24NUX +Relay:EE2-24SNU +Relay:EE2-24SNUH +Relay:EE2-24SNUX +Relay:EE2-24TNU +Relay:EE2-24TNUH +Relay:EE2-24TNUX +Relay:EE2-3NKX +Relay:EE2-3NU +Relay:EE2-3NUH +Relay:EE2-3NUX +Relay:EE2-3SNU +Relay:EE2-3SNUH +Relay:EE2-3SNUX +Relay:EE2-3TNU +Relay:EE2-3TNUH +Relay:EE2-3TNUX +Relay:EE2-4.5NKX +Relay:EE2-4.5NU +Relay:EE2-4.5NUH +Relay:EE2-4.5NUX +Relay:EE2-4.5SNU +Relay:EE2-4.5SNUH +Relay:EE2-4.5SNUX +Relay:EE2-4.5TNU +Relay:EE2-4.5TNUH +Relay:EE2-4.5TNUX +Relay:EE2-5NU +Relay:EE2-5NUH +Relay:EE2-5NUX +Relay:EE2-5SNU +Relay:EE2-5SNUH +Relay:EE2-5SNUX +Relay:EE2-5TNU +Relay:EE2-5TNUH +Relay:EE2-5TNUX +Relay:FINDER-30.22 +Relay:FINDER-32.21-x000 +Relay:FINDER-32.21-x300 +Relay:FINDER-34.51 +Relay:FINDER-34.51.7xxx.x019 +Relay:FINDER-36.11 +Relay:FINDER-40.11 +Relay:FINDER-40.11-2016 +Relay:FINDER-40.31 +Relay:FINDER-40.41 +Relay:FINDER-40.51 +Relay:FINDER-40.52 +Relay:FINDER-41.52 +Relay:FINDER-44.52 +Relay:FINDER-44.62 +Relay:FRT5 +Relay:FRT5_separated +Relay:Fujitsu_FTR-F1A +Relay:Fujitsu_FTR-F1C +Relay:Fujitsu_FTR-LYAA005x +Relay:Fujitsu_FTR-LYCA005x +Relay:G2RL-1 +Relay:G2RL-1-E +Relay:G2RL-1-H +Relay:G2RL-1A +Relay:G2RL-1A-E +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 +Relay:G6HU-2 +Relay:G6K-2 +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 +Relay:HF3-04 +Relay:HF3-05 +Relay:HF3-06 +Relay:HF3-07 +Relay:HF3-51 +Relay:HF3-52 +Relay:HF3-53 +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 +Relay:IM03 +Relay:IM04 +Relay:IM05 +Relay:IM06 +Relay:IM07 +Relay:IM08 +Relay:IM11 +Relay:IM12 +Relay:IM13 +Relay:IM16 +Relay:IM17 +Relay:IM21 +Relay:IM22 +Relay:IM23 +Relay:IM26 +Relay:IM40 +Relay:IM41 +Relay:IM42 +Relay:IM43 +Relay:IM44 +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 +Relay:RAYEX-L90B +Relay:RAYEX-L90BS +Relay:RAYEX-L90S +Relay:RM50-xx21 +Relay:RM84 +Relay:RSM822 +Relay:RT314A03 +Relay:RT314A05 +Relay:RT314A06 +Relay:RT314A12 +Relay:RT314A24 +Relay:RT42xAxx +Relay:RT42xFxx +Relay:RT42xxxx +Relay:RT44xxxx +Relay:RTE2xAxx +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 +Relay:SILxx-1Axx-71x +Relay:SILxx-1Bxx-71x +Relay:SILxx-1Cxx-51x +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 +Relay_SolidState:34.81-9024 +Relay_SolidState:AQH0213 +Relay_SolidState:AQH0213A +Relay_SolidState:AQH0223 +Relay_SolidState:AQH0223A +Relay_SolidState:AQH1213 +Relay_SolidState:AQH1213A +Relay_SolidState:AQH1223 +Relay_SolidState:AQH1223A +Relay_SolidState:AQH2213 +Relay_SolidState:AQH2213A +Relay_SolidState:AQH2223 +Relay_SolidState:AQH2223A +Relay_SolidState:AQH3213 +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 +Relay_SolidState:FOD420 +Relay_SolidState:FOD4208 +Relay_SolidState:FOD4216 +Relay_SolidState:FOD4218 +Relay_SolidState:FODM3011 +Relay_SolidState:FODM3012 +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 +Relay_SolidState:MOC3010M +Relay_SolidState:MOC3011M +Relay_SolidState:MOC3012M +Relay_SolidState:MOC3020M +Relay_SolidState:MOC3021M +Relay_SolidState:MOC3022M +Relay_SolidState:MOC3023M +Relay_SolidState:MOC3031M +Relay_SolidState:MOC3032M +Relay_SolidState:MOC3033M +Relay_SolidState:MOC3041M +Relay_SolidState:MOC3042M +Relay_SolidState:MOC3043M +Relay_SolidState:MOC3051M +Relay_SolidState:MOC3052M +Relay_SolidState:MOC3061M +Relay_SolidState:MOC3062M +Relay_SolidState:MOC3063M +Relay_SolidState:MOC3081M +Relay_SolidState:MOC3082M +Relay_SolidState:MOC3083M +Relay_SolidState:MOC3162M +Relay_SolidState:MOC3163M +Relay_SolidState:S102S01 +Relay_SolidState:S102S02 +Relay_SolidState:S112S01 +Relay_SolidState:S116S01 +Relay_SolidState:S116S02 +Relay_SolidState:S202S01 +Relay_SolidState:S202S02 +Relay_SolidState:S212S01 +Relay_SolidState:S216S01 +Relay_SolidState:S216S02 +Relay_SolidState:TLP141G +Relay_SolidState:TLP148G +Relay_SolidState:TLP160G +Relay_SolidState:TLP160J +Relay_SolidState:TLP161G +Relay_SolidState:TLP161J +Relay_SolidState:TLP175A +Relay_SolidState:TLP222A +Relay_SolidState:TLP222A-2 +Relay_SolidState:TLP3123 +Relay_SolidState:TLP3542 +Relay_SolidState:TLP3543 +Relay_SolidState:TLP3544 +Relay_SolidState:TLP3545 +Relay_SolidState:TLP3546 +RF:0900PC15J0013 +RF:ADC-10-1R +RF:ADCH-80 +RF:ADCH-80A +RF:ADL5904 +RF:ADP-2-1W +RF:AMK-2-13 +RF:AX5043 +RF:CC1000 +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 +RF:PAT1220-C-10DB +RF:PAT1220-C-1DB +RF:PAT1220-C-2DB +RF:PAT1220-C-3DB +RF:PAT1220-C-4DB +RF:PAT1220-C-5DB +RF:PAT1220-C-6DB +RF:PAT1220-C-7DB +RF:PAT1220-C-8DB +RF:PAT1220-C-9DB +RF:PD4859J5050S2HF +RF:RMK-3-451 +RF:RMK-5-51 +RF:SE5004L +RF:SX1231IMLTRT +RF:SX1261IMLTRT +RF:SX1262IMLTRT +RF:SX1272 +RF:SX1273 +RF:SX1276 +RF:SX1277 +RF:SX1278 +RF:SX1279 +RF:SYPD-1 +RF:SYPD-2 +RF:SYPD-52 +RF:Si4460 +RF:Si4461 +RF:Si4463 +RF:Si4464 +RF:TCP-2-10X +RF:nRF24L01P +RF_Amplifier:AD8313xRM +RF_Amplifier:ADL5541 +RF_Amplifier:ADL5542 +RF_Amplifier:ADL5610 +RF_Amplifier:BGA2800 +RF_Amplifier:BGA2801 +RF_Amplifier:BGA2803 +RF_Amplifier:BGA2815 +RF_Amplifier:BGA2817 +RF_Amplifier:BGA2818 +RF_Amplifier:BGA2850 +RF_Amplifier:BGA2851 +RF_Amplifier:BGA2865 +RF_Amplifier:BGA2866 +RF_Amplifier:BGA2867 +RF_Amplifier:BGA2869 +RF_Amplifier:BGA2870 +RF_Amplifier:BGA2874 +RF_Amplifier:CMX901 +RF_Amplifier:GALI-1 +RF_Amplifier:GALI-19 +RF_Amplifier:GALI-2 +RF_Amplifier:GALI-21 +RF_Amplifier:GALI-24 +RF_Amplifier:GALI-29 +RF_Amplifier:GALI-3 +RF_Amplifier:GALI-33 +RF_Amplifier:GALI-39 +RF_Amplifier:GALI-4 +RF_Amplifier:GALI-49 +RF_Amplifier:GALI-4F +RF_Amplifier:GALI-5 +RF_Amplifier:GALI-51 +RF_Amplifier:GALI-51F +RF_Amplifier:GALI-52 +RF_Amplifier:GALI-55 +RF_Amplifier:GALI-59 +RF_Amplifier:GALI-5F +RF_Amplifier:GALI-6 +RF_Amplifier:GALI-6F +RF_Amplifier:GALI-74 +RF_Amplifier:GALI-84 +RF_Amplifier:GALI-S66 +RF_Amplifier:GVA-123 +RF_Amplifier:GVA-60 +RF_Amplifier:GVA-62 +RF_Amplifier:GVA-63 +RF_Amplifier:GVA-81 +RF_Amplifier:GVA-82 +RF_Amplifier:GVA-83 +RF_Amplifier:GVA-84 +RF_Amplifier:GVA-93 +RF_Amplifier:HMC1099PM5E +RF_Amplifier:HMC8500PM5E +RF_Amplifier:MAX2679 +RF_Amplifier:MAX2679B +RF_Amplifier:MMZ09332BT1 +RF_Amplifier:PGA-102 +RF_Amplifier:PGA-1021 +RF_Amplifier:PGA-103 +RF_Amplifier:PGA-105 +RF_Amplifier:PGA-106-75 +RF_Amplifier:PGA-106R-75 +RF_Amplifier:PGA-122-75 +RF_Amplifier:PGA-32-75 +RF_Amplifier:PHA-1 +RF_Amplifier:PHA-101 +RF_Amplifier:PHA-13HLN +RF_Amplifier:PHA-13LN +RF_Amplifier:PHA-1H +RF_Amplifier:PHA-23HLN +RF_Amplifier:PHA-23LN +RF_Amplifier:QPL9547 +RF_Amplifier:SGL0622Z +RF_Amplifier:SKY65404 +RF_Amplifier:SPF5189Z +RF_Amplifier:TRF37A73 +RF_AM_FM:LA1185 +RF_AM_FM:MCS3142 +RF_AM_FM:SA605D +RF_AM_FM:SA605DK +RF_AM_FM:SA636DK +RF_AM_FM:Si4362 +RF_AM_FM:Si4730-D60-GU +RF_AM_FM:Si4731-D60-GU +RF_AM_FM:Si4734-D60-GU +RF_AM_FM:Si4735-D60-GU +RF_AM_FM:ZETA-433-SO +RF_AM_FM:ZETA-868-SO +RF_AM_FM:ZETA-915-SO +RF_Bluetooth:BL652 +RF_Bluetooth:BM78SPPS5MC2 +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 +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 +RF_Filter:BFCN-1560 +RF_Filter:BFCN-1575 +RF_Filter:BFCN-1690 +RF_Filter:BFCN-1840 +RF_Filter:BFCN-1855 +RF_Filter:BFCN-1860 +RF_Filter:BFCN-1900 +RF_Filter:BFCN-1945 +RF_Filter:BFCN-2275 +RF_Filter:BFCN-2360 +RF_Filter:BFCN-2435 +RF_Filter:BFCN-2450 +RF_Filter:BFCN-2500 +RF_Filter:BFCN-2555 +RF_Filter:BFCN-2700 +RF_Filter:BFCN-2840 +RF_Filter:BFCN-2850 +RF_Filter:BFCN-2900 +RF_Filter:BFCN-2910 +RF_Filter:BFCN-2975 +RF_Filter:BFCN-3010 +RF_Filter:BFCN-3085 +RF_Filter:BFCN-3085A +RF_Filter:BFCN-3115 +RF_Filter:BFCN-3600 +RF_Filter:BFCN-3700 +RF_Filter:BFCN-4100 +RF_Filter:BFCN-4440 +RF_Filter:BFCN-4800 +RF_Filter:BFCN-5100 +RF_Filter:BFCN-5200 +RF_Filter:BFCN-5540 +RF_Filter:BFCN-5750 +RF_Filter:BFCN-7200 +RF_Filter:BFCN-7331 +RF_Filter:BFCN-7350 +RF_Filter:BFCN-7500 +RF_Filter:BFCN-7700 +RF_Filter:BFCN-7900 +RF_Filter:BFCN-8000 +RF_Filter:BFCN-8350 +RF_Filter:BFCN-8450 +RF_Filter:BFCN-8650 +RF_Filter:BPF-A355 +RF_Filter:HFCN-1000 +RF_Filter:HFCN-1080 +RF_Filter:HFCN-1100 +RF_Filter:HFCN-1150 +RF_Filter:HFCN-1200 +RF_Filter:HFCN-1200D +RF_Filter:HFCN-1300 +RF_Filter:HFCN-1300D +RF_Filter:HFCN-1320 +RF_Filter:HFCN-1320D +RF_Filter:HFCN-1322 +RF_Filter:HFCN-1500 +RF_Filter:HFCN-1500D +RF_Filter:HFCN-1600 +RF_Filter:HFCN-1600D +RF_Filter:HFCN-1760 +RF_Filter:HFCN-1810 +RF_Filter:HFCN-1810D +RF_Filter:HFCN-1910 +RF_Filter:HFCN-1910D +RF_Filter:HFCN-2000 +RF_Filter:HFCN-2100 +RF_Filter:HFCN-2100D +RF_Filter:HFCN-2275 +RF_Filter:HFCN-2700 +RF_Filter:HFCN-2700A +RF_Filter:HFCN-2700AD +RF_Filter:HFCN-3100 +RF_Filter:HFCN-3100D +RF_Filter:HFCN-3500 +RF_Filter:HFCN-3500D +RF_Filter:HFCN-3800 +RF_Filter:HFCN-3800D +RF_Filter:HFCN-440 +RF_Filter:HFCN-4400 +RF_Filter:HFCN-4400D +RF_Filter:HFCN-4600 +RF_Filter:HFCN-5050 +RF_Filter:HFCN-5500 +RF_Filter:HFCN-5500D +RF_Filter:HFCN-6010 +RF_Filter:HFCN-650 +RF_Filter:HFCN-650D +RF_Filter:HFCN-672 +RF_Filter:HFCN-7150 +RF_Filter:HFCN-740 +RF_Filter:HFCN-740D +RF_Filter:HFCN-7971 +RF_Filter:HFCN-8400 +RF_Filter:HFCN-8400D +RF_Filter:HFCN-880 +RF_Filter:HFCN-880D +RF_Filter:HFCN-9700 +RF_Filter:LFCN-1000 +RF_Filter:LFCN-1000D +RF_Filter:LFCN-105 +RF_Filter:LFCN-113 +RF_Filter:LFCN-120 +RF_Filter:LFCN-1200 +RF_Filter:LFCN-1200D +RF_Filter:LFCN-123 +RF_Filter:LFCN-1282 +RF_Filter:LFCN-1325 +RF_Filter:LFCN-1400 +RF_Filter:LFCN-1400D +RF_Filter:LFCN-1450 +RF_Filter:LFCN-1500 +RF_Filter:LFCN-1500D +RF_Filter:LFCN-1525 +RF_Filter:LFCN-1525D +RF_Filter:LFCN-1575 +RF_Filter:LFCN-1575D +RF_Filter:LFCN-160 +RF_Filter:LFCN-1700 +RF_Filter:LFCN-1700D +RF_Filter:LFCN-180 +RF_Filter:LFCN-1800 +RF_Filter:LFCN-1800D +RF_Filter:LFCN-190 +RF_Filter:LFCN-2000 +RF_Filter:LFCN-2000D +RF_Filter:LFCN-225 +RF_Filter:LFCN-2250 +RF_Filter:LFCN-2250D +RF_Filter:LFCN-225D +RF_Filter:LFCN-2290 +RF_Filter:LFCN-2400 +RF_Filter:LFCN-2400D +RF_Filter:LFCN-2500 +RF_Filter:LFCN-2500D +RF_Filter:LFCN-2600 +RF_Filter:LFCN-2600D +RF_Filter:LFCN-2750 +RF_Filter:LFCN-2750D +RF_Filter:LFCN-2850 +RF_Filter:LFCN-2850D +RF_Filter:LFCN-3000 +RF_Filter:LFCN-3000D +RF_Filter:LFCN-320 +RF_Filter:LFCN-320D +RF_Filter:LFCN-3400 +RF_Filter:LFCN-3400D +RF_Filter:LFCN-3800 +RF_Filter:LFCN-3800D +RF_Filter:LFCN-400 +RF_Filter:LFCN-400D +RF_Filter:LFCN-4400 +RF_Filter:LFCN-4400D +RF_Filter:LFCN-490 +RF_Filter:LFCN-490D +RF_Filter:LFCN-5000 +RF_Filter:LFCN-5000D +RF_Filter:LFCN-530 +RF_Filter:LFCN-530D +RF_Filter:LFCN-5500 +RF_Filter:LFCN-5500D +RF_Filter:LFCN-575 +RF_Filter:LFCN-575D +RF_Filter:LFCN-5850 +RF_Filter:LFCN-5850D +RF_Filter:LFCN-6000 +RF_Filter:LFCN-6000D +RF_Filter:LFCN-630 +RF_Filter:LFCN-630D +RF_Filter:LFCN-6400 +RF_Filter:LFCN-6400D +RF_Filter:LFCN-6700 +RF_Filter:LFCN-6700D +RF_Filter:LFCN-7200 +RF_Filter:LFCN-7200D +RF_Filter:LFCN-722 +RF_Filter:LFCN-80 +RF_Filter:LFCN-800 +RF_Filter:LFCN-800D +RF_Filter:LFCN-8400 +RF_Filter:LFCN-8440 +RF_Filter:LFCN-900 +RF_Filter:LFCN-900D +RF_Filter:LFCN-9170 +RF_Filter:LFCN-95 +RF_Filter:LPF-B0R3 +RF_Filter:RBP-280 +RF_Filter:RBPF-246 +RF_Filter:RLP-30 +RF_Filter:SCHF-31 +RF_Filter:STA0232A +RF_Filter:STA1090EC +RF_Filter:SXBP-100 +RF_Filter:SXBP-140 +RF_Filter:SXBP-202 +RF_Filter:SXBP-27R5 +RF_Filter:TA0232A +RF_Filter:TA0970B +RF_GPS:L70-R +RF_GPS:L80-R +RF_GPS:LEA-M8F +RF_GPS:LEA-M8S +RF_GPS:LEA-M8T +RF_GPS:MAX-8C +RF_GPS:MAX-8Q +RF_GPS:MAX-M10S +RF_GPS:MAX-M8C +RF_GPS:MAX-M8Q +RF_GPS:MAX-M8W +RF_GPS:NEO-8Q +RF_GPS:NEO-M8M +RF_GPS:NEO-M8N +RF_GPS:NEO-M8P +RF_GPS:NEO-M8Q +RF_GPS:NEO-M8T +RF_GPS:NEO-M9N +RF_GPS:RXM-GPS-FM +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 +RF_GSM:SIM900 +RF_GSM:UL865 +RF_Mixer:AD831AP +RF_Mixer:ADE-6 +RF_Mixer:ADEX-10 +RF_Mixer:ADL5801 +RF_Mixer:ADL5802 +RF_Mixer:HMC213A +RF_Mixer:HMC213B +RF_Mixer:LT5560 +RF_Module:AST50147-xx +RF_Module:ATSAMR21G18-MR210UA_NoRFPads +RF_Module:AX-SIP-SFEU +RF_Module:AX-SIP-SFEU-API +RF_Module:Ai-Thinker-Ra-01 +RF_Module:Ai-Thinker-Ra-02 +RF_Module:CMWX1ZZABZ-078 +RF_Module:CMWX1ZZABZ-091 +RF_Module:D52MxxM8 +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 +RF_Module:ESP-07 +RF_Module:ESP-12E +RF_Module:ESP-12F +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-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 +RF_Module:MDBT50Q-P512K +RF_Module:MDBT50Q-U1MV2 +RF_Module:MDBT50Q-U512K +RF_Module:MM002 +RF_Module:Particle_P1 +RF_Module:RAK4200 +RF_Module:RAK811-HF-AS923 +RF_Module:RAK811-HF-AU915 +RF_Module:RAK811-HF-EU868 +RF_Module:RAK811-HF-IN865 +RF_Module:RAK811-HF-KR920 +RF_Module:RAK811-HF-US915 +RF_Module:RAK811-LF-CN470 +RF_Module:RAK811-LF-EU433 +RF_Module:RFM69HCW +RF_Module:RFM69HW +RF_Module:RFM69W +RF_Module:RFM95W-868S2 +RF_Module:RFM95W-915S2 +RF_Module:RFM96W-315S2 +RF_Module:RFM96W-433S2 +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 +RF_NFC:ST25DV04K-IER8C3 +RF_NFC:ST25DV04K-JFR6D3 +RF_NFC:ST25DV16K-IER8C3 +RF_NFC:ST25DV16K-JFR6D3 +RF_NFC:ST25DV64K-IER8C3 +RF_NFC:ST25DV64K-JFR6D3 +RF_NFC:ST25R3911B-AQF +RF_NFC:ST25R3911B-AQW +RF_RFID:HTRC11001T +RF_RFID:PN5120A0HN1 +RF_Switch:ADG901BCPZ +RF_Switch:ADG901BRMZ +RF_Switch:ADG902BRMZ +RF_Switch:ADG918BCPZ +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 +RF_Switch:KSWA-2-46 +RF_Switch:KSWHA-1-20 +RF_Switch:MASW-007221 +RF_Switch:MASWSS0115 +RF_Switch:MASWSS0136 +RF_Switch:MASWSS0143 +RF_Switch:MASWSS0151 +RF_Switch:MASWSS0166 +RF_Switch:MASWSS0176 +RF_Switch:MASWSS0178 +RF_Switch:MASWSS0179 +RF_Switch:MASWSS0192 +RF_Switch:MSW-2-20 +RF_Switch:MSW2-50 +RF_Switch:MSWA-2-20 +RF_Switch:MSWA2-50 +RF_Switch:SKY13380-350LF +RF_Switch:SKY13575-639LF +RF_WiFi:HF-A11-SMT +RF_WiFi:USR-C322 +RF_ZigBee:CC2520 +RF_ZigBee:MC13192 +RF_ZigBee:MW-R-DP-W +RF_ZigBee:MW-R-WX +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 +Sensor:AM2302 +Sensor:APDS-9960 +Sensor:AS3935 +Sensor:BL0937 +Sensor:BME280 +Sensor:BME680 +Sensor:CHT11 +Sensor:DHT11 +Sensor:INA260 +Sensor:LTC2990 +Sensor:MAX30102 +Sensor:Nuclear-Radiation_Detector +Sensor:RPR-0521RS +Sensor:SHT1x +Sensor_Audio:ICS-43434 +Sensor_Audio:IM69D120 +Sensor_Audio:IM69D130 +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 +Sensor_Current:A1363xKTTN-5 +Sensor_Current:A1365xKTTN-1 +Sensor_Current:A1365xKTTN-10 +Sensor_Current:A1365xKTTN-2 +Sensor_Current:A1365xKTTN-5 +Sensor_Current:A1366xKTTN-1 +Sensor_Current:A1366xKTTN-10 +Sensor_Current:A1366xKTTN-2 +Sensor_Current:A1366xKTTN-5 +Sensor_Current:A1367xKTTN-1 +Sensor_Current:A1367xKTTN-10 +Sensor_Current:A1367xKTTN-2 +Sensor_Current:A1367xKTTN-5 +Sensor_Current:A1369xUA-10 +Sensor_Current:A1369xUA-24 +Sensor_Current:ACS706xLC-05C +Sensor_Current:ACS709xLFTR-10BB +Sensor_Current:ACS709xLFTR-20BB +Sensor_Current:ACS709xLFTR-35BB +Sensor_Current:ACS709xLFTR-6BB +Sensor_Current:ACS710xLATR-10BB +Sensor_Current:ACS710xLATR-10BB-NL +Sensor_Current:ACS710xLATR-12BB +Sensor_Current:ACS710xLATR-12BB-NL +Sensor_Current:ACS710xLATR-25BB +Sensor_Current:ACS710xLATR-25BB-NL +Sensor_Current:ACS710xLATR-6BB +Sensor_Current:ACS710xLATR-6BB-NL +Sensor_Current:ACS711xEXLT-15AB +Sensor_Current:ACS711xEXLT-31AB +Sensor_Current:ACS711xLCTR-12AB +Sensor_Current:ACS711xLCTR-25AB +Sensor_Current:ACS712xLCTR-05B +Sensor_Current:ACS712xLCTR-20A +Sensor_Current:ACS712xLCTR-30A +Sensor_Current:ACS713xLCTR-20A +Sensor_Current:ACS713xLCTR-30A +Sensor_Current:ACS714xLCTR-05B +Sensor_Current:ACS714xLCTR-20A +Sensor_Current:ACS714xLCTR-30A +Sensor_Current:ACS714xLCTR-50A +Sensor_Current:ACS715xLCTR-20A +Sensor_Current:ACS715xLCTR-30A +Sensor_Current:ACS716xLATR-12BB +Sensor_Current:ACS716xLATR-12BB-NL +Sensor_Current:ACS716xLATR-25BB +Sensor_Current:ACS716xLATR-25BB-NL +Sensor_Current:ACS716xLATR-6BB +Sensor_Current:ACS716xLATR-6BB-NL +Sensor_Current:ACS717xMATR-10B +Sensor_Current:ACS717xMATR-20B +Sensor_Current:ACS718xMATR-10B +Sensor_Current:ACS718xMATR-20B +Sensor_Current:ACS720xMATR-15B +Sensor_Current:ACS720xMATR-35B +Sensor_Current:ACS720xMATR-65B +Sensor_Current:ACS722xLCTR-05AB +Sensor_Current:ACS722xLCTR-10AB +Sensor_Current:ACS722xLCTR-10AU +Sensor_Current:ACS722xLCTR-20AB +Sensor_Current:ACS722xLCTR-20AU +Sensor_Current:ACS722xLCTR-40AB +Sensor_Current:ACS722xLCTR-40AU +Sensor_Current:ACS722xMATR-10AB +Sensor_Current:ACS722xMATR-20AB +Sensor_Current:ACS722xMATR-40AB +Sensor_Current:ACS723xLCTR-05AB +Sensor_Current:ACS723xLCTR-10AB +Sensor_Current:ACS723xLCTR-10AU +Sensor_Current:ACS723xLCTR-20AB +Sensor_Current:ACS723xLCTR-20AU +Sensor_Current:ACS723xLCTR-40AB +Sensor_Current:ACS723xLCTR-40AU +Sensor_Current:ACS723xMATR-10AB +Sensor_Current:ACS723xMATR-20AB +Sensor_Current:ACS723xMATR-40AB +Sensor_Current:ACS724xLCTR-05AB +Sensor_Current:ACS724xLCTR-10AB +Sensor_Current:ACS724xLCTR-10AU +Sensor_Current:ACS724xLCTR-20AB +Sensor_Current:ACS724xLCTR-20AU +Sensor_Current:ACS724xLCTR-30AB +Sensor_Current:ACS724xLCTR-30AU +Sensor_Current:ACS724xLCTR-50AB +Sensor_Current:ACS724xMATR-12AB +Sensor_Current:ACS724xMATR-20AB +Sensor_Current:ACS724xMATR-30AB +Sensor_Current:ACS724xMATR-30AU +Sensor_Current:ACS724xMATR-65AB +Sensor_Current:ACS725xLCTR-10AU +Sensor_Current:ACS725xLCTR-20AB +Sensor_Current:ACS725xLCTR-20AU +Sensor_Current:ACS725xLCTR-30AB +Sensor_Current:ACS725xLCTR-30AU +Sensor_Current:ACS725xLCTR-40AB +Sensor_Current:ACS725xLCTR-50AB +Sensor_Current:ACS725xMATR-20AB +Sensor_Current:ACS725xMATR-30AB +Sensor_Current:ACS725xMATR-30AU +Sensor_Current:ACS726xLFTR-20B +Sensor_Current:ACS726xLFTR-40B +Sensor_Current:ACS730xLCTR-20AB +Sensor_Current:ACS730xLCTR-40AB +Sensor_Current:ACS730xLCTR-40AU +Sensor_Current:ACS730xLCTR-50AB +Sensor_Current:ACS730xLCTR-80AU +Sensor_Current:ACS732xLATR-40AB +Sensor_Current:ACS73369xUAA-010B5 +Sensor_Current:ACS733xLATR-20AB +Sensor_Current:ACS733xLATR-40AB +Sensor_Current:ACS733xLATR-40AU +Sensor_Current:ACS733xLATR-65AB +Sensor_Current:ACS756xCB-050B-PFF +Sensor_Current:ACS756xCB-100B-PFF +Sensor_Current:ACS758xCB-050B-PFF +Sensor_Current:ACS758xCB-050U-PFF +Sensor_Current:ACS758xCB-100B-PFF +Sensor_Current:ACS758xCB-100B-PSF +Sensor_Current:ACS758xCB-100U-PFF +Sensor_Current:ACS758xCB-150B-PFF +Sensor_Current:ACS758xCB-150B-PSS +Sensor_Current:ACS758xCB-150U-PFF +Sensor_Current:ACS758xCB-150U-PSF +Sensor_Current:ACS758xCB-200B-PFF +Sensor_Current:ACS758xCB-200B-PSF +Sensor_Current:ACS758xCB-200B-PSS +Sensor_Current:ACS758xCB-200U-PFF +Sensor_Current:ACS758xCB-200U-PSF +Sensor_Current:ACS759xCB-050B-PFF +Sensor_Current:ACS759xCB-100B-PFF +Sensor_Current:ACS759xCB-150B-PFF +Sensor_Current:ACS759xCB-150B-PSS +Sensor_Current:ACS759xCB-200B-PFF +Sensor_Current:ACS759xCB-200B-PSS +Sensor_Current:ACS770xCB-050B-PFF +Sensor_Current:ACS770xCB-050U-PFF +Sensor_Current:ACS770xCB-100B-PFF +Sensor_Current:ACS770xCB-100U-PFF +Sensor_Current:ACS770xCB-100U-PSF +Sensor_Current:ACS770xCB-150B-PFF +Sensor_Current:ACS770xCB-150B-PSF +Sensor_Current:ACS770xCB-150U-PFF +Sensor_Current:ACS770xCB-150U-PSF +Sensor_Current:ACS770xCB-200B-PFF +Sensor_Current:ACS770xCB-200B-PSF +Sensor_Current:ACS770xCB-200U-PFF +Sensor_Current:ACS770xCB-200U-PSF +Sensor_Current:ACS780xLRTR-050B +Sensor_Current:ACS780xLRTR-050U +Sensor_Current:ACS780xLRTR-100B +Sensor_Current:ACS780xLRTR-100U +Sensor_Current:ACS780xLRTR-150B +Sensor_Current:ACS780xLRTR-150U +Sensor_Current:ACS781xLRTR-050B +Sensor_Current:ACS781xLRTR-050U +Sensor_Current:ACS781xLRTR-100B +Sensor_Current:ACS781xLRTR-100U +Sensor_Current:ACS781xLRTR-150B +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 +Sensor_Current:CQ-206A +Sensor_Current:CQ-206B +Sensor_Current:CQ-2092 +Sensor_Current:CQ-2093 +Sensor_Current:CQ-209A +Sensor_Current:CQ-209B +Sensor_Current:CQ-209D +Sensor_Current:CQ-2232 +Sensor_Current:CQ-2233 +Sensor_Current:CQ-2234 +Sensor_Current:CQ-2235 +Sensor_Current:CQ-2332 +Sensor_Current:CQ-2333 +Sensor_Current:CQ-2334 +Sensor_Current:CQ-2335 +Sensor_Current:CQ-2336 +Sensor_Current:CQ-236B +Sensor_Current:CQ-3200 +Sensor_Current:CQ-3201 +Sensor_Current:CQ-3202 +Sensor_Current:CQ-3203 +Sensor_Current:CQ-3204 +Sensor_Current:CQ-320A +Sensor_Current:CQ-320B +Sensor_Current:CQ-3300 +Sensor_Current:CQ-3301 +Sensor_Current:CQ-3302 +Sensor_Current:CQ-3303 +Sensor_Current:CQ-330A +Sensor_Current:CQ-330B +Sensor_Current:CQ-330E +Sensor_Current:CQ-330F +Sensor_Current:CQ-330G +Sensor_Current:CQ-330H +Sensor_Current:CQ-330J +Sensor_Current:CSLW6B1 +Sensor_Current:CSLW6B200M +Sensor_Current:CSLW6B40M +Sensor_Current:CSLW6B5 +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 +Sensor_Current:HO150-NP +Sensor_Current:HO25-NP +Sensor_Current:HO25-NPxSP33 +Sensor_Current:HO25-NSM +Sensor_Current:HO40-NP +Sensor_Current:HO60-NP +Sensor_Current:HO8-NP +Sensor_Current:HO8-NPxSP33 +Sensor_Current:HO8-NSM +Sensor_Current:HTFS200-P +Sensor_Current:HTFS400-P +Sensor_Current:HTFS600-P +Sensor_Current:HTFS800-P +Sensor_Current:HX02-P +Sensor_Current:HX03-P-SP2 +Sensor_Current:HX04-P +Sensor_Current:HX05-NP +Sensor_Current:HX05-P-SP2 +Sensor_Current:HX06-P +Sensor_Current:HX10-NP +Sensor_Current:HX10-P-SP2 +Sensor_Current:HX15-NP +Sensor_Current:HX15-P-SP2 +Sensor_Current:HX20-P-SP2 +Sensor_Current:HX25-P-SP2 +Sensor_Current:HX50-P-SP2 +Sensor_Current:IR2175 +Sensor_Current:IR21771S +Sensor_Current:IR2177S +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 +Sensor_Energy:PAC1932x-xJ6CX +Sensor_Energy:PAC1932x-xJQ +Sensor_Energy:PAC1933x-xJ6CX +Sensor_Energy:PAC1933x-xJQ +Sensor_Energy:PAC1934x-xJ6CX +Sensor_Energy:PAC1934x-xJQ +Sensor_Energy:PAC1941x-1x-4MX +Sensor_Energy:PAC1941x-1x-J6CX +Sensor_Energy:PAC1941x-2x-4MX +Sensor_Energy:PAC1941x-2x-J6CX +Sensor_Energy:PAC1942x-1x-4MX +Sensor_Energy:PAC1942x-1x-J6CX +Sensor_Energy:PAC1942x-2x-4MX +Sensor_Energy:PAC1942x-2x-J6CX +Sensor_Energy:PAC1943x-x4MX +Sensor_Energy:PAC1943x-xJ6CX +Sensor_Energy:PAC1944x-x4MX +Sensor_Energy:PAC1944x-xJ6CX +Sensor_Energy:PAC1951x-1x-4MX +Sensor_Energy:PAC1951x-1x-J6CX +Sensor_Energy:PAC1951x-2x-4MX +Sensor_Energy:PAC1951x-2x-J6CX +Sensor_Energy:PAC1952x-1x-4MX +Sensor_Energy:PAC1952x-1x-J6CX +Sensor_Energy:PAC1952x-2x-4MX +Sensor_Energy:PAC1952x-2x-J6CX +Sensor_Energy:PAC1953x-x4MX +Sensor_Energy:PAC1953x-xJ6CX +Sensor_Energy:PAC1954x-x4MX +Sensor_Energy:PAC1954x-xJ6CX +Sensor_Gas:004-0-0010 +Sensor_Gas:004-0-0013 +Sensor_Gas:004-0-0050 +Sensor_Gas:004-0-0053 +Sensor_Gas:004-0-0071 +Sensor_Gas:004-0-0075 +Sensor_Gas:3SP-H2S-50_110-304 +Sensor_Gas:CCS811 +Sensor_Gas:GM-402B +Sensor_Gas:LuminOX_LOX-O2 +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 +Sensor_Humidity:SHT30-DIS +Sensor_Humidity:SHT30A-DIS +Sensor_Humidity:SHT31-DIS +Sensor_Humidity:SHT31A-DIS +Sensor_Humidity:SHT35-DIS +Sensor_Humidity:SHT35A-DIS +Sensor_Humidity:SHT4x +Sensor_Humidity:SHTC1 +Sensor_Humidity:SHTC3 +Sensor_Humidity:Si7020-A20 +Sensor_Humidity:Si7021-A20 +Sensor_Magnetic:A1101ELHL +Sensor_Magnetic:A1101LLHL +Sensor_Magnetic:A1102ELHL +Sensor_Magnetic:A1102LLHL +Sensor_Magnetic:A1103ELHL +Sensor_Magnetic:A1103LLHL +Sensor_Magnetic:A1104LLHL +Sensor_Magnetic:A1106LLHL +Sensor_Magnetic:A1301EUA-T +Sensor_Magnetic:A1301KLHLT-T +Sensor_Magnetic:A1301KUA-T +Sensor_Magnetic:A1302ELHLT-T +Sensor_Magnetic:A1302KLHLT-T +Sensor_Magnetic:A1302KUA-T +Sensor_Magnetic:A3214ELHLT-T +Sensor_Magnetic:AH1806-P +Sensor_Magnetic:AH1806-W +Sensor_Magnetic:AH1806-Z +Sensor_Magnetic:AK7452 +Sensor_Magnetic:AS5045B +Sensor_Magnetic:AS5047D +Sensor_Magnetic:AS5048A +Sensor_Magnetic:AS5048B +Sensor_Magnetic:AS5050A +Sensor_Magnetic:AS5055A +Sensor_Magnetic:BM1422AGMV +Sensor_Magnetic:BMM150 +Sensor_Magnetic:DRV5033AJxDBZ +Sensor_Magnetic:DRV5033AJxLPG +Sensor_Magnetic:DRV5033FAxDBZ +Sensor_Magnetic:DRV5033FAxLPG +Sensor_Magnetic:DRV5055A1xDBZxQ1 +Sensor_Magnetic:DRV5055A1xLPGxQ1 +Sensor_Magnetic:DRV5055A2xDBZxQ1 +Sensor_Magnetic:DRV5055A2xLPGxQ1 +Sensor_Magnetic:DRV5055A3xDBZxQ1 +Sensor_Magnetic:DRV5055A3xLPGxQ1 +Sensor_Magnetic:DRV5055A4xDBZxQ1 +Sensor_Magnetic:DRV5055A4xLPGxQ1 +Sensor_Magnetic:IST8308 +Sensor_Magnetic:IST8310 +Sensor_Magnetic:LIS2MDL +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 +Sensor_Magnetic:TMAG5110B2xxDBV +Sensor_Magnetic:TMAG5110B4xxDBV +Sensor_Magnetic:TMAG5110C2xxDBV +Sensor_Magnetic:TMAG5110C4xxDBV +Sensor_Magnetic:TMAG5111A2xxDBV +Sensor_Magnetic:TMAG5111A4xxDBV +Sensor_Magnetic:TMAG5111B2xxDBV +Sensor_Magnetic:TMAG5111B4xxDBV +Sensor_Magnetic:TMAG5111C2xxDBV +Sensor_Magnetic:TMAG5111C4xxDBV +Sensor_Magnetic:TMAG5170-Q1 +Sensor_Motion:ADXL343 +Sensor_Motion:ADXL363 +Sensor_Motion:BMF055 +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 +Sensor_Motion:KXTJ3-1057 +Sensor_Motion:L3GD20 +Sensor_Motion:LIS2DE12 +Sensor_Motion:LIS2DH +Sensor_Motion:LIS2HH12 +Sensor_Motion:LIS331HH +Sensor_Motion:LIS3DH +Sensor_Motion:LSM303C +Sensor_Motion:LSM303D +Sensor_Motion:LSM303DLHC +Sensor_Motion:LSM6DS3 +Sensor_Motion:LSM6DSL +Sensor_Motion:LSM6DSM +Sensor_Motion:LSM9DS1 +Sensor_Motion:MMA8653FCR1 +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 +Sensor_Optical:AS7261 +Sensor_Optical:AS7262 +Sensor_Optical:AS7263 +Sensor_Optical:AS72651 +Sensor_Optical:AS7341DLG +Sensor_Optical:AS7343xDLG +Sensor_Optical:BP103 +Sensor_Optical:BP103B +Sensor_Optical:BP103BF +Sensor_Optical:BP104 +Sensor_Optical:BP104-SMD +Sensor_Optical:BPW21 +Sensor_Optical:BPW34 +Sensor_Optical:BPW34-SMD +Sensor_Optical:BPW40 +Sensor_Optical:BPW42 +Sensor_Optical:BPW82 +Sensor_Optical:BPW85 +Sensor_Optical:BPW85A +Sensor_Optical:BPW85B +Sensor_Optical:BPW85C +Sensor_Optical:BPX61 +Sensor_Optical:BPX65 +Sensor_Optical:BPY62 +Sensor_Optical:C12880MA +Sensor_Optical:Flir_LEPTON +Sensor_Optical:ISL29035 +Sensor_Optical:KPS-3227 +Sensor_Optical:KPS-5130 +Sensor_Optical:LDR03 +Sensor_Optical:LDR07 +Sensor_Optical:LPT80A +Sensor_Optical:LTR-303ALS-01 +Sensor_Optical:M9960 +Sensor_Optical:NOA1305 +Sensor_Optical:PMTx08Dyn +Sensor_Optical:PMTx08Dyn_Shld +Sensor_Optical:S13360-3025CS +Sensor_Optical:S13360-3050CS +Sensor_Optical:S13360-3075CS +Sensor_Optical:S5971 +Sensor_Optical:S5972 +Sensor_Optical:S5973 +Sensor_Optical:SFH203 +Sensor_Optical:SFH203FA +Sensor_Optical:SFH205F +Sensor_Optical:SFH205FA +Sensor_Optical:SFH206K +Sensor_Optical:SFH216 +Sensor_Optical:SFH225FA +Sensor_Optical:SFH235FA +Sensor_Optical:SFH2400 +Sensor_Optical:SFH2430 +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 +Sensor_Pressure:MPL115A1 +Sensor_Pressure:MPL3115A2 +Sensor_Pressure:MPXA6115A +Sensor_Pressure:MPXAZ6115A +Sensor_Pressure:MPXH6115A +Sensor_Pressure:MPXHZ6115A +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 +Sensor_Proximity:ITR8307-L24-TR8 +Sensor_Proximity:ITR8307-S17-TR8 +Sensor_Proximity:ITR9608-F +Sensor_Proximity:KRC011 +Sensor_Proximity:LDC1312 +Sensor_Proximity:LDC1314 +Sensor_Proximity:LDC1612 +Sensor_Proximity:LDC1614 +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 +Sensor_Proximity:SFH9206 +Sensor_Proximity:SG-105 +Sensor_Proximity:SG-105F +Sensor_Proximity:SG-107 +Sensor_Proximity:SG-107F +Sensor_Proximity:TSSP58038 +Sensor_Proximity:TSSP58038SS1XB +Sensor_Proximity:TSSP58P38 +Sensor_Temperature:AD8494 +Sensor_Temperature:AD8495 +Sensor_Temperature:AD8496 +Sensor_Temperature:AD8497 +Sensor_Temperature:BD1020HFV +Sensor_Temperature:DS1621 +Sensor_Temperature:DS1621S +Sensor_Temperature:DS1621V +Sensor_Temperature:DS1804 +Sensor_Temperature:DS1821C +Sensor_Temperature:DS1822 +Sensor_Temperature:DS1822-PAR +Sensor_Temperature:DS1822Z +Sensor_Temperature:DS1825 +Sensor_Temperature:DS18B20 +Sensor_Temperature:DS18B20-PAR +Sensor_Temperature:DS18B20U +Sensor_Temperature:DS18B20Z +Sensor_Temperature:DS18S20 +Sensor_Temperature:DS18S20-PAR +Sensor_Temperature:DS18S20Z +Sensor_Temperature:DS28EA00 +Sensor_Temperature:KT100 +Sensor_Temperature:KTY10 +Sensor_Temperature:KTY81 +Sensor_Temperature:KTY82 +Sensor_Temperature:KTY83 +Sensor_Temperature:KTY84 +Sensor_Temperature:KTY85 +Sensor_Temperature:LM20BIM7 +Sensor_Temperature:LM20CIM7 +Sensor_Temperature:LM35-D +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 +Sensor_Temperature:LMT87DCK +Sensor_Temperature:LTC2983 +Sensor_Temperature:MAX31820 +Sensor_Temperature:MAX31820PAR +Sensor_Temperature:MAX31826 +Sensor_Temperature:MAX31855EASA +Sensor_Temperature:MAX31855JASA +Sensor_Temperature:MAX31855KASA +Sensor_Temperature:MAX31855NASA +Sensor_Temperature:MAX31855RASA +Sensor_Temperature:MAX31855SASA +Sensor_Temperature:MAX31855TASA +Sensor_Temperature:MAX31856 +Sensor_Temperature:MAX31865xAP +Sensor_Temperature:MAX31865xTP +Sensor_Temperature:MAX6654 +Sensor_Temperature:MCP9501 +Sensor_Temperature:MCP9502 +Sensor_Temperature:MCP9503 +Sensor_Temperature:MCP9504 +Sensor_Temperature:MCP9700Ax-ELT +Sensor_Temperature:MCP9700Ax-ETT +Sensor_Temperature:MCP9700Ax-HLT +Sensor_Temperature:MCP9700Ax-HTT +Sensor_Temperature:MCP9700x-ELT +Sensor_Temperature:MCP9700x-ETT +Sensor_Temperature:MCP9700x-HLT +Sensor_Temperature:MCP9700x-HTT +Sensor_Temperature:MCP9800Ax-xOT +Sensor_Temperature:MCP9802Ax-xOT +Sensor_Temperature:MCP9804_DFN +Sensor_Temperature:MCP9804_MSOP +Sensor_Temperature:MCP9808_DFN +Sensor_Temperature:MCP9808_MSOP +Sensor_Temperature:MCP9844x-xMN +Sensor_Temperature:PCT2075D +Sensor_Temperature:PCT2075DP +Sensor_Temperature:PT100 +Sensor_Temperature:PT1000 +Sensor_Temperature:PT500 +Sensor_Temperature:Si7050-A20 +Sensor_Temperature:Si7051-A20 +Sensor_Temperature:Si7053-A20 +Sensor_Temperature:Si7054-A20 +Sensor_Temperature:Si7055-A20 +Sensor_Temperature:TC1047AxNB +Sensor_Temperature:TC1047xNB +Sensor_Temperature:TMP100 +Sensor_Temperature:TMP101 +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 +Sensor_Temperature:TMP411 +Sensor_Temperature:TMP461xxRUN +Sensor_Temperature:TMP464xxRGT +Sensor_Temperature:TMP468xxRGT +Sensor_Temperature:TSIC206-SO8 +Sensor_Temperature:TSIC206-TO92 +Sensor_Temperature:TSIC306-SO8 +Sensor_Temperature:TSIC306-TO92 +Sensor_Touch:AT42QT1010-M +Sensor_Touch:AT42QT1010-TSHR +Sensor_Touch:AT42QT1011-M +Sensor_Touch:AT42QT1011-TSHR +Sensor_Touch:AT42QT1012-M +Sensor_Touch:AT42QT1012-T +Sensor_Touch:AT42QT1040-M +Sensor_Touch:AT42QT1050-M +Sensor_Touch:AT42QT1050-U +Sensor_Touch:AT42QT1060-M +Sensor_Touch:AT42QT1070-M +Sensor_Touch:AT42QT1070-S +Sensor_Touch:AT42QT1110-M +Sensor_Touch:CAP1206-x-AIA +Sensor_Touch:CAP1206-x-SL +Sensor_Touch:CY8CMBR3002 +Sensor_Touch:CY8CMBR3102 +Sensor_Touch:CY8CMBR3106S +Sensor_Touch:CY8CMBR3108 +Sensor_Touch:CY8CMBR3110 +Sensor_Touch:CY8CMBR3116 +Sensor_Touch:MPR121QR2 +Sensor_Touch:PCA8886 +Sensor_Touch:PCF8883 +Sensor_Voltage:LV25-P +Simulation_SPICE:0 +Simulation_SPICE:BSOURCE +Simulation_SPICE:D +Simulation_SPICE:ESOURCE +Simulation_SPICE:GSOURCE +Simulation_SPICE:IAM +Simulation_SPICE:IBIS_DEVICE +Simulation_SPICE:IBIS_DEVICE_DIFF +Simulation_SPICE:IBIS_DRIVER +Simulation_SPICE:IBIS_DRIVER_DIFF +Simulation_SPICE:IDC +Simulation_SPICE:IEXP +Simulation_SPICE:IPULSE +Simulation_SPICE:IPWL +Simulation_SPICE:ISFFM +Simulation_SPICE:ISIN +Simulation_SPICE:ITRNOISE +Simulation_SPICE:ITRRANDOM +Simulation_SPICE:NJFET +Simulation_SPICE:NMOS +Simulation_SPICE:NMOS_Substrate +Simulation_SPICE:NPN +Simulation_SPICE:NPN_Substrate +Simulation_SPICE:OPAMP +Simulation_SPICE:PJFET +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 +Simulation_SPICE:VEXP +Simulation_SPICE:VOLTMETER_DIFF +Simulation_SPICE:VPULSE +Simulation_SPICE:VPWL +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 +Switch:SW_Coded_SH-7040 +Switch:SW_Coded_SH-7050 +Switch:SW_Coded_SH-7070 +Switch:SW_Coded_SH-7080 +Switch:SW_DIP_x01 +Switch:SW_DIP_x02 +Switch:SW_DIP_x03 +Switch:SW_DIP_x04 +Switch:SW_DIP_x05 +Switch:SW_DIP_x06 +Switch:SW_DIP_x07 +Switch:SW_DIP_x08 +Switch:SW_DIP_x09 +Switch:SW_DIP_x10 +Switch:SW_DIP_x11 +Switch:SW_DIP_x12 +Switch:SW_DP3T +Switch:SW_DPDT_x2 +Switch:SW_DPST +Switch:SW_DPST_Temperature +Switch:SW_DPST_x2 +Switch:SW_E3_SA3216 +Switch:SW_E3_SA3624 +Switch:SW_E3_SA6432 +Switch:SW_MEC_5E +Switch:SW_MEC_5G +Switch:SW_MEC_5G_2LED +Switch:SW_MEC_5G_LED +Switch:SW_MMI_Q5-100 +Switch:SW_NKK_GW12LJPCF +Switch:SW_Nidec_CAS-120A1 +Switch:SW_Omron_B3FS +Switch:SW_Push +Switch:SW_Push_45deg +Switch:SW_Push_DPDT +Switch:SW_Push_Dual +Switch:SW_Push_Dual_x2 +Switch:SW_Push_LED +Switch:SW_Push_Lamp +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_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 +Timer:82C54 +Timer:82C54_PLCC +Timer:AD9513 +Timer:AD9514 +Timer:AD9515 +Timer:CD4541BE +Timer:CD4541BM +Timer:CD4541BPW +Timer:DS1023S +Timer:ICM7209 +Timer:ICM7555xB +Timer:ICM7555xP +Timer:ICM7556 +Timer:LM555xM +Timer:LM555xMM +Timer:LM555xN +Timer:LM556 +Timer:LMC555xM +Timer:LMC555xMM +Timer:LMC555xN +Timer:LMC555xTP +Timer:LTC6902 +Timer:LTC6909 +Timer:LTC6993xS6-1 +Timer:LTC6993xS6-2 +Timer:LTC6993xS6-3 +Timer:LTC6993xS6-4 +Timer:LTC6994xDCB-1 +Timer:LTC6994xDCB-2 +Timer:LTC6994xS6-1 +Timer:LTC6994xS6-2 +Timer:MC14541BD +Timer:MC14541BDT +Timer:MC1455B +Timer:MC1455P +Timer:MN3101 +Timer:MN3102 +Timer:NA555D +Timer:NA555P +Timer:NA556 +Timer:NE555D +Timer:NE555P +Timer:NE556 +Timer:NE567 +Timer:NLV14541BD +Timer:NLV14541BDT +Timer:PL611-01-xxxT +Timer:SA555D +Timer:SA555P +Timer:SA556 +Timer:SE555D +Timer:SE555P +Timer:SE556 +Timer:SE567 +Timer:SY58031U +Timer:SY58032U +Timer:SY58033U +Timer:TLC555xD +Timer:TLC555xP +Timer:TLC555xPS +Timer:TLC555xPW +Timer:TPL5010 +Timer:TPL5110 +Timer:TPL5111 +Timer_PLL:ADF4002BCPZ +Timer_PLL:ADF4002BRUZ +Timer_PLL:ADF4158 +Timer_PLL:ADF4350 +Timer_PLL:ADF4351 +Timer_PLL:CDCVF2505 +Timer_PLL:CS2000-CP +Timer_PLL:ICS525-01R +Timer_PLL:ICS525R-02 +Timer_PLL:Si5342A-D +Timer_PLL:Si5342B-D +Timer_PLL:Si5342C-D +Timer_PLL:Si5342D-D +Timer_PLL:Si5344A-D +Timer_PLL:Si5344B-D +Timer_PLL:Si5344C-D +Timer_PLL:Si5344D-D +Timer_PLL:Si5345A-D +Timer_PLL:Si5345B-D +Timer_PLL:Si5345C-D +Timer_PLL:Si5345D-D +Timer_RTC:AB0805 +Timer_RTC:AB0815 +Timer_RTC:AB1805 +Timer_RTC:AB1815 +Timer_RTC:BQ32000 +Timer_RTC:BQ32002 +Timer_RTC:DS1302+ +Timer_RTC:DS1302N+ +Timer_RTC:DS1302S+ +Timer_RTC:DS1302SN+ +Timer_RTC:DS1302Z+ +Timer_RTC:DS1302ZN+ +Timer_RTC:DS1307+ +Timer_RTC:DS1307N+ +Timer_RTC:DS1307Z+ +Timer_RTC:DS1307ZN+ +Timer_RTC:DS1602 +Timer_RTC:DS3231M +Timer_RTC:DS3231MZ +Timer_RTC:DS3232M +Timer_RTC:M41T62Q +Timer_RTC:MCP7940N-xMNY +Timer_RTC:MCP7940N-xMS +Timer_RTC:MCP7940N-xP +Timer_RTC:MCP7940N-xSN +Timer_RTC:MCP7940N-xST +Timer_RTC:PCF85063ATL +Timer_RTC:PCF8523T +Timer_RTC:PCF8523TK +Timer_RTC:PCF8523TS +Timer_RTC:PCF85263AT +Timer_RTC:PCF85263ATL +Timer_RTC:PCF85263ATT +Timer_RTC:PCF85263ATT1 +Timer_RTC:PCF85363ATT +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 +Transformer:0915BM15A0001 +Transformer:30F-51NL +Transformer:5400BL15B050 +Transformer:ADT1-1 +Transformer:ADT1-1WT +Transformer:ADT1-1WT-1 +Transformer:ADT1-6T +Transformer:ADT1.5-1 +Transformer:ADT1.5-122 +Transformer:ADT1.5-17 +Transformer:ADT1.5-2 +Transformer:ADT16-1T +Transformer:ADT16-6 +Transformer:ADT16-6T +Transformer:ADT2-1T +Transformer:ADT2-1T-1P +Transformer:ADT2-71T +Transformer:ADT3-1T +Transformer:ADT3-1T-75 +Transformer:ADT3-6T +Transformer:ADT4-1T +Transformer:ADT4-1WT +Transformer:ADT4-5WT +Transformer:ADT4-6 +Transformer:ADT4-6T +Transformer:ADT4-6WT +Transformer:ADT8-1T +Transformer:ADT9-1T +Transformer:ADTL1-12 +Transformer:ADTL1-15-75 +Transformer:ADTL1-18-75 +Transformer:ADTL1-4-75 +Transformer:ADTL2-18 +Transformer:ADTT1-1 +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 +Transformer:P0926NL +Transformer:PA0173NL +Transformer:PA0184NL +Transformer:PA0184NL1 +Transformer:PA0185NL +Transformer:PA0185NL1 +Transformer:PA0264NL +Transformer:PA0297NL +Transformer:PA0510NL +Transformer:PA1323NL +Transformer:PA2001NL +Transformer:PA2002NL +Transformer:PA2004NL +Transformer:PA2005NL +Transformer:PA2006NL +Transformer:PA2007NL +Transformer:PA2008NL +Transformer:PA2009NL +Transformer:PA2777NL +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 +Transformer:TEZ1.5-D-2 +Transformer:TEZ10.0-D-1 +Transformer:TEZ10.0-D-2 +Transformer:TEZ16.0-D-1 +Transformer:TEZ16.0-D-2 +Transformer:TEZ2.0-D-1 +Transformer:TEZ2.0-D-2 +Transformer:TEZ2.5-D-1 +Transformer:TEZ2.5-D-2 +Transformer:TEZ2.6-D-1 +Transformer:TEZ2.6-D-2 +Transformer:TEZ4.0-D-1 +Transformer:TEZ4.0-D-2 +Transformer:TEZ6.0-D-1 +Transformer:TEZ6.0-D-2 +Transformer:TG110-E050N5xx +Transformer:TG110-S050N2xx +Transformer:TG111-MSC13LF +Transformer:TR1-SO8 +Transformer:TR60_FC +Transformer:TR60_IC +Transformer:TR60_IC2 +Transformer:TRANSF1 +Transformer:TRANSF2 +Transformer:TRANSF3 +Transformer:TRANSF4 +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 +Transistor_Array:MC1413D +Transistor_Array:MC1413P +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 +Transistor_Array:ULN2003A +Transistor_Array:ULN2004 +Transistor_Array:ULN2004A +Transistor_Array:ULN2801A +Transistor_Array:ULN2802A +Transistor_Array:ULN2803A +Transistor_Array:ULN2804A +Transistor_Array:ULN2805A +Transistor_Array:ULQ2003A +Transistor_Array:ULQ2004A +Transistor_BJT:2N2219 +Transistor_BJT:2N2646 +Transistor_BJT:2N2647 +Transistor_BJT:2N3055 +Transistor_BJT:2N3904 +Transistor_BJT:2N3905 +Transistor_BJT:2N3906 +Transistor_BJT:2SA1015 +Transistor_BJT:2SB631 +Transistor_BJT:2SB817 +Transistor_BJT:2SC1815 +Transistor_BJT:2SC1941 +Transistor_BJT:2SC1945 +Transistor_BJT:2SC4213 +Transistor_BJT:2SD1047 +Transistor_BJT:2SD600 +Transistor_BJT:320S14-U +Transistor_BJT:BC107 +Transistor_BJT:BC108 +Transistor_BJT:BC109 +Transistor_BJT:BC140 +Transistor_BJT:BC141 +Transistor_BJT:BC160 +Transistor_BJT:BC161 +Transistor_BJT:BC212 +Transistor_BJT:BC237 +Transistor_BJT:BC240 +Transistor_BJT:BC307 +Transistor_BJT:BC327 +Transistor_BJT:BC328 +Transistor_BJT:BC337 +Transistor_BJT:BC338 +Transistor_BJT:BC413 +Transistor_BJT:BC413B +Transistor_BJT:BC413C +Transistor_BJT:BC414 +Transistor_BJT:BC414B +Transistor_BJT:BC414C +Transistor_BJT:BC516 +Transistor_BJT:BC517 +Transistor_BJT:BC546 +Transistor_BJT:BC547 +Transistor_BJT:BC548 +Transistor_BJT:BC549 +Transistor_BJT:BC550 +Transistor_BJT:BC556 +Transistor_BJT:BC557 +Transistor_BJT:BC558 +Transistor_BJT:BC559 +Transistor_BJT:BC560 +Transistor_BJT:BC636 +Transistor_BJT:BC807 +Transistor_BJT:BC807W +Transistor_BJT:BC808 +Transistor_BJT:BC808W +Transistor_BJT:BC817 +Transistor_BJT:BC817W +Transistor_BJT:BC818 +Transistor_BJT:BC818W +Transistor_BJT:BC846 +Transistor_BJT:BC846BDW1 +Transistor_BJT:BC846BPDW1 +Transistor_BJT:BC846BPN +Transistor_BJT:BC846BS +Transistor_BJT:BC847 +Transistor_BJT:BC847BDW1 +Transistor_BJT:BC847BPDW1 +Transistor_BJT:BC847BPN +Transistor_BJT:BC847BS +Transistor_BJT:BC847W +Transistor_BJT:BC848 +Transistor_BJT:BC848W +Transistor_BJT:BC849 +Transistor_BJT:BC849W +Transistor_BJT:BC850 +Transistor_BJT:BC850W +Transistor_BJT:BC856 +Transistor_BJT:BC856BDW1 +Transistor_BJT:BC856BS +Transistor_BJT:BC856W +Transistor_BJT:BC857 +Transistor_BJT:BC857BDW1 +Transistor_BJT:BC857BS +Transistor_BJT:BC857W +Transistor_BJT:BC858 +Transistor_BJT:BC858W +Transistor_BJT:BC859 +Transistor_BJT:BC859W +Transistor_BJT:BC860 +Transistor_BJT:BC860W +Transistor_BJT:BCP51 +Transistor_BJT:BCP53 +Transistor_BJT:BCP56 +Transistor_BJT:BCV29 +Transistor_BJT:BCV49 +Transistor_BJT:BCV61 +Transistor_BJT:BCV62 +Transistor_BJT:BCX51 +Transistor_BJT:BCX52 +Transistor_BJT:BCX53 +Transistor_BJT:BCX56 +Transistor_BJT:BD135 +Transistor_BJT:BD136 +Transistor_BJT:BD137 +Transistor_BJT:BD138 +Transistor_BJT:BD139 +Transistor_BJT:BD140 +Transistor_BJT:BD233 +Transistor_BJT:BD234 +Transistor_BJT:BD235 +Transistor_BJT:BD236 +Transistor_BJT:BD237 +Transistor_BJT:BD238 +Transistor_BJT:BD249 +Transistor_BJT:BD249A +Transistor_BJT:BD249B +Transistor_BJT:BD249C +Transistor_BJT:BD250 +Transistor_BJT:BD250A +Transistor_BJT:BD250B +Transistor_BJT:BD250C +Transistor_BJT:BD433 +Transistor_BJT:BD434 +Transistor_BJT:BD435 +Transistor_BJT:BD436 +Transistor_BJT:BD437 +Transistor_BJT:BD438 +Transistor_BJT:BD439 +Transistor_BJT:BD440 +Transistor_BJT:BD441 +Transistor_BJT:BD442 +Transistor_BJT:BD909 +Transistor_BJT:BD910 +Transistor_BJT:BD911 +Transistor_BJT:BD912 +Transistor_BJT:BDW93 +Transistor_BJT:BDW93A +Transistor_BJT:BDW93B +Transistor_BJT:BDW93C +Transistor_BJT:BDW94 +Transistor_BJT:BDW94A +Transistor_BJT:BDW94B +Transistor_BJT:BDW94C +Transistor_BJT:BF199 +Transistor_BJT:BF457 +Transistor_BJT:BF458 +Transistor_BJT:BF459 +Transistor_BJT:BFR92 +Transistor_BJT:BFT92 +Transistor_BJT:BUT11 +Transistor_BJT:BUT11A +Transistor_BJT:DMMT5401 +Transistor_BJT:DTA113T +Transistor_BJT:DTA113Z +Transistor_BJT:DTA114E +Transistor_BJT:DTA114G +Transistor_BJT:DTA114T +Transistor_BJT:DTA114W +Transistor_BJT:DTA114Y +Transistor_BJT:DTA115E +Transistor_BJT:DTA115G +Transistor_BJT:DTA115T +Transistor_BJT:DTA115U +Transistor_BJT:DTA123E +Transistor_BJT:DTA123J +Transistor_BJT:DTA123Y +Transistor_BJT:DTA124E +Transistor_BJT:DTA124G +Transistor_BJT:DTA124T +Transistor_BJT:DTA124X +Transistor_BJT:DTA125T +Transistor_BJT:DTA143E +Transistor_BJT:DTA143T +Transistor_BJT:DTA143X +Transistor_BJT:DTA143Y +Transistor_BJT:DTA143Z +Transistor_BJT:DTA144E +Transistor_BJT:DTA144G +Transistor_BJT:DTA144T +Transistor_BJT:DTA144V +Transistor_BJT:DTA144W +Transistor_BJT:DTA1D3R +Transistor_BJT:DTA214Y +Transistor_BJT:DTB113E +Transistor_BJT:DTB113Z +Transistor_BJT:DTB114E +Transistor_BJT:DTB114G +Transistor_BJT:DTB114T +Transistor_BJT:DTB122J +Transistor_BJT:DTB123E +Transistor_BJT:DTB123T +Transistor_BJT:DTB123Y +Transistor_BJT:DTB133H +Transistor_BJT:DTB143T +Transistor_BJT:DTB163T +Transistor_BJT:DTC113T +Transistor_BJT:DTC113Z +Transistor_BJT:DTC114E +Transistor_BJT:DTC114G +Transistor_BJT:DTC114T +Transistor_BJT:DTC114W +Transistor_BJT:DTC114Y +Transistor_BJT:DTC115E +Transistor_BJT:DTC115G +Transistor_BJT:DTC115T +Transistor_BJT:DTC115U +Transistor_BJT:DTC123E +Transistor_BJT:DTC123J +Transistor_BJT:DTC123Y +Transistor_BJT:DTC124E +Transistor_BJT:DTC124G +Transistor_BJT:DTC124T +Transistor_BJT:DTC124X +Transistor_BJT:DTC125T +Transistor_BJT:DTC143E +Transistor_BJT:DTC143T +Transistor_BJT:DTC143X +Transistor_BJT:DTC143Y +Transistor_BJT:DTC143Z +Transistor_BJT:DTC144E +Transistor_BJT:DTC144G +Transistor_BJT:DTC144T +Transistor_BJT:DTC144V +Transistor_BJT:DTC144W +Transistor_BJT:DTC1D3R +Transistor_BJT:DTC214Y +Transistor_BJT:DTD113E +Transistor_BJT:DTD113Z +Transistor_BJT:DTD114E +Transistor_BJT:DTD114G +Transistor_BJT:DTD114T +Transistor_BJT:DTD122J +Transistor_BJT:DTD123E +Transistor_BJT:DTD123T +Transistor_BJT:DTD123Y +Transistor_BJT:DTD133H +Transistor_BJT:DTD143T +Transistor_BJT:DTD163T +Transistor_BJT:EMH3 +Transistor_BJT:FFB2222A +Transistor_BJT:FFB2227A +Transistor_BJT:FFB3904 +Transistor_BJT:FFB3906 +Transistor_BJT:FFB3946 +Transistor_BJT:FFB5551 +Transistor_BJT:FMB2227A +Transistor_BJT:FMB3946 +Transistor_BJT:IMH3A +Transistor_BJT:KTD1624 +Transistor_BJT:MAT02 +Transistor_BJT:MBT2222ADW1T1 +Transistor_BJT:MBT3904DW1 +Transistor_BJT:MBT3906DW1 +Transistor_BJT:MBT3946DW1T1 +Transistor_BJT:MJ2955 +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 +Transistor_BJT:PMBT3946YPN +Transistor_BJT:PN2222A +Transistor_BJT:PUMT1 +Transistor_BJT:PUMX1 +Transistor_BJT:PZT2222A +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 +Transistor_BJT:TIP121 +Transistor_BJT:TIP122 +Transistor_BJT:TIP125 +Transistor_BJT:TIP126 +Transistor_BJT:TIP127 +Transistor_BJT:TIP2955 +Transistor_BJT:TIP2955G +Transistor_BJT:TIP3055 +Transistor_BJT:TIP3055G +Transistor_BJT:TIP41 +Transistor_BJT:TIP41A +Transistor_BJT:TIP41B +Transistor_BJT:TIP41C +Transistor_BJT:TIP42 +Transistor_BJT:TIP42A +Transistor_BJT:TIP42B +Transistor_BJT:TIP42C +Transistor_BJT:UMH3N +Transistor_FET:2N3819 +Transistor_FET:2N7000 +Transistor_FET:2N7002 +Transistor_FET:2N7002E +Transistor_FET:2N7002H +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 +Transistor_FET:BF244C +Transistor_FET:BF245A +Transistor_FET:BF245B +Transistor_FET:BF245C +Transistor_FET:BF545A +Transistor_FET:BF545B +Transistor_FET:BF545C +Transistor_FET:BF994S +Transistor_FET:BS107 +Transistor_FET:BS108 +Transistor_FET:BS170 +Transistor_FET:BS170F +Transistor_FET:BS250 +Transistor_FET:BS870 +Transistor_FET:BSB008NE2LX +Transistor_FET:BSB012NE2LXI +Transistor_FET:BSB013NE2LXI +Transistor_FET:BSB014N04LX3 +Transistor_FET:BSB015N04NX3 +Transistor_FET:BSB028N06NN3 +Transistor_FET:BSB044N08NN3 +Transistor_FET:BSB056N10NN3 +Transistor_FET:BSB104N08NP3 +Transistor_FET:BSB165N15NZ3 +Transistor_FET:BSB280N15NZ3 +Transistor_FET:BSC026N08NS5 +Transistor_FET:BSC028N06LS3 +Transistor_FET:BSC030N08NS5 +Transistor_FET:BSC035N10NS5 +Transistor_FET:BSC037N08NS5 +Transistor_FET:BSC040N08NS5 +Transistor_FET:BSC040N10NS5 +Transistor_FET:BSC046N10NS3G +Transistor_FET:BSC047N08NS3G +Transistor_FET:BSC052N08NS5 +Transistor_FET:BSC057N08NS3G +Transistor_FET:BSC060N10NS3G +Transistor_FET:BSC061N08NS5 +Transistor_FET:BSC070N10NS3G +Transistor_FET:BSC070N10NS5 +Transistor_FET:BSC072N08NS5 +Transistor_FET:BSC079N10NSG +Transistor_FET:BSC082N10LSG +Transistor_FET:BSC098N10NS5 +Transistor_FET:BSC100N10NSFG +Transistor_FET:BSC105N10LSFG +Transistor_FET:BSC109N10NS3G +Transistor_FET:BSC117N08NS5 +Transistor_FET:BSC118N10NSG +Transistor_FET:BSC123N08NS3G +Transistor_FET:BSC123N10LSG +Transistor_FET:BSC13DN30NSFD +Transistor_FET:BSC159N10LSFG +Transistor_FET:BSC160N10NS3G +Transistor_FET:BSC196N10NSG +Transistor_FET:BSC252N10NSFG +Transistor_FET:BSC265N10LSFG +Transistor_FET:BSC340N08NS3G +Transistor_FET:BSC440N10NS3G +Transistor_FET:BSD235C +Transistor_FET:BSD840N +Transistor_FET:BSF030NE2LQ +Transistor_FET:BSF035NE2LQ +Transistor_FET:BSF450NE7NH3 +Transistor_FET:BSN20 +Transistor_FET:BSP129 +Transistor_FET:BSP89 +Transistor_FET:BSR56 +Transistor_FET:BSR57 +Transistor_FET:BSR58 +Transistor_FET:BSS123 +Transistor_FET:BSS127S +Transistor_FET:BSS138 +Transistor_FET:BSS214NW +Transistor_FET:BSS83P +Transistor_FET:BSS84 +Transistor_FET:BUK7880-55A +Transistor_FET:BUK7M10-40EX +Transistor_FET:BUK7M12-40EX +Transistor_FET:BUK7M12-60EX +Transistor_FET:BUK7M15-60EX +Transistor_FET:BUK7M17-80EX +Transistor_FET:BUK7M19-60EX +Transistor_FET:BUK7M21-40EX +Transistor_FET:BUK7M22-80EX +Transistor_FET:BUK7M27-80EX +Transistor_FET:BUK7M33-60EX +Transistor_FET:BUK7M42-60EX +Transistor_FET:BUK7M45-40EX +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 +Transistor_FET:BUK9M120-100EX +Transistor_FET:BUK9M14-40EX +Transistor_FET:BUK9M15-60EX +Transistor_FET:BUK9M156-100EX +Transistor_FET:BUK9M17-30EX +Transistor_FET:BUK9M19-60EX +Transistor_FET:BUK9M23-80EX +Transistor_FET:BUK9M24-40EX +Transistor_FET:BUK9M24-60EX +Transistor_FET:BUK9M28-80EX +Transistor_FET:BUK9M34-100EX +Transistor_FET:BUK9M35-80EX +Transistor_FET:BUK9M42-60EX +Transistor_FET:BUK9M43-100EX +Transistor_FET:BUK9M52-40EX +Transistor_FET:BUK9M53-60EX +Transistor_FET:BUK9M5R2-30EX +Transistor_FET:BUK9M6R6-30EX +Transistor_FET:BUK9M7R2-40EX +Transistor_FET:BUK9M85-60EX +Transistor_FET:BUK9M9R1-40EX +Transistor_FET:BUZ11 +Transistor_FET:C2M0025120D +Transistor_FET:C2M0040120D +Transistor_FET:C2M0045170D +Transistor_FET:C2M0080120D +Transistor_FET:C2M0160120D +Transistor_FET:C2M0280120D +Transistor_FET:C2M1000170D +Transistor_FET:C2M1000170J +Transistor_FET:C3M0030090K +Transistor_FET:C3M0065090D +Transistor_FET:C3M0065090J +Transistor_FET:C3M0065100J +Transistor_FET:C3M0065100K +Transistor_FET:C3M0075120J +Transistor_FET:C3M0075120K +Transistor_FET:C3M0120090D +Transistor_FET:C3M0120090J +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 +Transistor_FET:CSD16404Q5A +Transistor_FET:CSD16407Q5 +Transistor_FET:CSD16408Q5 +Transistor_FET:CSD16410Q5A +Transistor_FET:CSD16412Q5A +Transistor_FET:CSD16413Q5A +Transistor_FET:CSD16414Q5 +Transistor_FET:CSD16415Q5 +Transistor_FET:CSD16570Q5B +Transistor_FET:CSD17301Q5A +Transistor_FET:CSD17302Q5A +Transistor_FET:CSD17303Q5 +Transistor_FET:CSD17305Q5A +Transistor_FET:CSD17306Q5A +Transistor_FET:CSD17307Q5A +Transistor_FET:CSD17310Q5A +Transistor_FET:CSD17311Q5 +Transistor_FET:CSD17312Q5 +Transistor_FET:CSD17313Q2 +Transistor_FET:CSD17322Q5A +Transistor_FET:CSD17327Q5A +Transistor_FET:CSD17501Q5A +Transistor_FET:CSD17505Q5A +Transistor_FET:CSD17506Q5A +Transistor_FET:CSD17507Q5A +Transistor_FET:CSD17510Q5A +Transistor_FET:CSD17522Q5A +Transistor_FET:CSD17527Q5A +Transistor_FET:CSD17551Q5A +Transistor_FET:CSD17552Q5A +Transistor_FET:CSD17553Q5A +Transistor_FET:CSD17555Q5A +Transistor_FET:CSD17556Q5B +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 +Transistor_FET:CSD18504Q5A +Transistor_FET:CSD18509Q5B +Transistor_FET:CSD18531Q5A +Transistor_FET:CSD18532NQ5B +Transistor_FET:CSD18532Q5B +Transistor_FET:CSD18533Q5A +Transistor_FET:CSD18534Q5A +Transistor_FET:CSD18537NQ5A +Transistor_FET:CSD18540Q5B +Transistor_FET:CSD18543Q3A +Transistor_FET:CSD18563Q5A +Transistor_FET:CSD19502Q5B +Transistor_FET:CSD19531Q5A +Transistor_FET:CSD19532Q5B +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 +Transistor_FET:DMG2301L +Transistor_FET:DMG2302U +Transistor_FET:DMG3402L +Transistor_FET:DMG3404L +Transistor_FET:DMG3406L +Transistor_FET:DMG3414U +Transistor_FET:DMG3418L +Transistor_FET:DMG9926UDM +Transistor_FET:DMN10H220L +Transistor_FET:DMN10H700S +Transistor_FET:DMN13H750S +Transistor_FET:DMN2040U +Transistor_FET:DMN2041L +Transistor_FET:DMN2050L +Transistor_FET:DMN2056U +Transistor_FET:DMN2058U +Transistor_FET:DMN2075U +Transistor_FET:DMN2230U +Transistor_FET:DMN24H11DS +Transistor_FET:DMN24H3D5L +Transistor_FET:DMN3008SFG +Transistor_FET:DMN3033LDM +Transistor_FET:DMN3042L +Transistor_FET:DMN3051L +Transistor_FET:DMN30H4D0L +Transistor_FET:DMN3110S +Transistor_FET:DMN3150L +Transistor_FET:DMN32D2LDF +Transistor_FET:DMN3300U +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 +Transistor_FET:FDG1024NZ +Transistor_FET:FDG6335N +Transistor_FET:FDMC8032L +Transistor_FET:FDMS8050 +Transistor_FET:FDMS8050ET30 +Transistor_FET:FDMS8350L +Transistor_FET:FDMS8350LET40 +Transistor_FET:FDMS86150 +Transistor_FET:FDMS86150ET100 +Transistor_FET:FDMS86152 +Transistor_FET:FDMS86202 +Transistor_FET:FDMS86202ET120 +Transistor_FET:FDMS86255 +Transistor_FET:FDMS86255ET150 +Transistor_FET:FDMS86350 +Transistor_FET:FDMS86350ET80 +Transistor_FET:FDMS86550 +Transistor_FET:FDMS86550ET60 +Transistor_FET:FDMT800100DC +Transistor_FET:FDMT800120DC +Transistor_FET:FDMT800150DC +Transistor_FET:FDMT800152DC +Transistor_FET:FDMT80060DC +Transistor_FET:FDMT80080DC +Transistor_FET:FDN340P +Transistor_FET:FDS2734 +Transistor_FET:FDS4559 +Transistor_FET:FDS4897AC +Transistor_FET:FDS4897C +Transistor_FET:FDS6630A +Transistor_FET:FDS6890A +Transistor_FET:FDS6892A +Transistor_FET:FDS6898A +Transistor_FET:FDS6930A +Transistor_FET:FDS6930B +Transistor_FET:FDS8960C +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 +Transistor_FET:IGOT60R070D1 +Transistor_FET:IGT40R070D1_E8220 +Transistor_FET:IGT60R070D1 +Transistor_FET:IGT60R190D1S +Transistor_FET:IPB180N10S4-02 +Transistor_FET:IPD50R380CE +Transistor_FET:IPD50R3K0CE +Transistor_FET:IPDD60R050G7 +Transistor_FET:IPDD60R080G7 +Transistor_FET:IPDD60R102G7 +Transistor_FET:IPDD60R125G7 +Transistor_FET:IPDD60R150G7 +Transistor_FET:IPDD60R190G7 +Transistor_FET:IPP060N06N +Transistor_FET:IPT012N08N5 +Transistor_FET:IPT015N10N5 +Transistor_FET:IPT020N10N3 +Transistor_FET:IRF3205 +Transistor_FET:IRF40DM229 +Transistor_FET:IRF4905 +Transistor_FET:IRF540N +Transistor_FET:IRF60DM206 +Transistor_FET:IRF6613 +Transistor_FET:IRF6614 +Transistor_FET:IRF6616 +Transistor_FET:IRF6617 +Transistor_FET:IRF6618 +Transistor_FET:IRF6620 +Transistor_FET:IRF6621 +Transistor_FET:IRF6622 +Transistor_FET:IRF6623 +Transistor_FET:IRF6628 +Transistor_FET:IRF6631 +Transistor_FET:IRF6635 +Transistor_FET:IRF6636 +Transistor_FET:IRF6637 +Transistor_FET:IRF6641 +Transistor_FET:IRF6643 +Transistor_FET:IRF6644 +Transistor_FET:IRF6646 +Transistor_FET:IRF6648 +Transistor_FET:IRF6655 +Transistor_FET:IRF6662 +Transistor_FET:IRF6665 +Transistor_FET:IRF6668 +Transistor_FET:IRF6674 +Transistor_FET:IRF6710S2 +Transistor_FET:IRF6711S +Transistor_FET:IRF6712S +Transistor_FET:IRF6713S +Transistor_FET:IRF6714M +Transistor_FET:IRF6715M +Transistor_FET:IRF6716M +Transistor_FET:IRF6717M +Transistor_FET:IRF6718L2 +Transistor_FET:IRF6721S +Transistor_FET:IRF6722M +Transistor_FET:IRF6724M +Transistor_FET:IRF6725M +Transistor_FET:IRF6726M +Transistor_FET:IRF6727M +Transistor_FET:IRF6728M +Transistor_FET:IRF6775M +Transistor_FET:IRF6785 +Transistor_FET:IRF6795M +Transistor_FET:IRF6797M +Transistor_FET:IRF6798M +Transistor_FET:IRF6802SD +Transistor_FET:IRF6810S +Transistor_FET:IRF6811S +Transistor_FET:IRF6892S +Transistor_FET:IRF6893M +Transistor_FET:IRF6894M +Transistor_FET:IRF6898M +Transistor_FET:IRF7171M +Transistor_FET:IRF7309IPBF +Transistor_FET:IRF7324 +Transistor_FET:IRF7343PBF +Transistor_FET:IRF740 +Transistor_FET:IRF7403 +Transistor_FET:IRF7404 +Transistor_FET:IRF7480M +Transistor_FET:IRF7483M +Transistor_FET:IRF7486M +Transistor_FET:IRF7580M +Transistor_FET:IRF7606PBF +Transistor_FET:IRF7607PBF +Transistor_FET:IRF7665S2 +Transistor_FET:IRF7739L1 +Transistor_FET:IRF7748L1 +Transistor_FET:IRF7759L2 +Transistor_FET:IRF7769L1 +Transistor_FET:IRF7779L2 +Transistor_FET:IRF7780M +Transistor_FET:IRF7799L2 +Transistor_FET:IRF7946 +Transistor_FET:IRF8301M +Transistor_FET:IRF8302M +Transistor_FET:IRF8304M +Transistor_FET:IRF8306M +Transistor_FET:IRF8308M +Transistor_FET:IRF8327S +Transistor_FET:IRF8721PBF-1 +Transistor_FET:IRF9383M +Transistor_FET:IRF9540N +Transistor_FET:IRFI4019H +Transistor_FET:IRFI4020H +Transistor_FET:IRFI4212H +Transistor_FET:IRFP4468PbF +Transistor_FET:IRFP4668PbF +Transistor_FET:IRFS4115 +Transistor_FET:IRFS4127 +Transistor_FET:IRFS4227 +Transistor_FET:IRFS4229 +Transistor_FET:IRFS4310Z +Transistor_FET:IRFS4321 +Transistor_FET:IRFTS9342PBF +Transistor_FET:IRL6283M +Transistor_FET:IRL6297SD +Transistor_FET:IRL7472L1 +Transistor_FET:IRLB8721PBF +Transistor_FET:IRLIZ44N +Transistor_FET:IRLML0030 +Transistor_FET:IRLML2060 +Transistor_FET:IRLML5203 +Transistor_FET:IRLML6244 +Transistor_FET:IRLML6401 +Transistor_FET:IRLML6402 +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 +Transistor_FET:MMBF4393 +Transistor_FET:MMBFJ111 +Transistor_FET:MMBFJ112 +Transistor_FET:MMBFJ113 +Transistor_FET:NDT3055L +Transistor_FET:NTR2101P +Transistor_FET:PGA26E07BA +Transistor_FET:PGA26E19BA +Transistor_FET:PMDT290UCE +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 +Transistor_FET:STD7NK40Z +Transistor_FET:STS2DNE60 +Transistor_FET:SUD08P06-155L +Transistor_FET:SUD09P10-195 +Transistor_FET:SUD19P06-60 +Transistor_FET:SUD45P03-09 +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 +Transistor_FET:Si3456DDV +Transistor_FET:Si4162DY +Transistor_FET:Si4532DY +Transistor_FET:Si4542DY +Transistor_FET:Si7141DP +Transistor_FET:Si7336ADP +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 +Transistor_FET:TSM2302CX +Transistor_FET:VN10LF +Transistor_FET:VNP10N07 +Transistor_FET:VP0610L +Transistor_FET:VP0610T +Transistor_FET:ZVN3306F +Transistor_FET:ZVN3310F +Transistor_FET:ZVN3320F +Transistor_FET:ZVN4106F +Transistor_FET:ZXM61N02F +Transistor_FET:ZXM61N03F +Transistor_FET:ZXMN10A07F +Transistor_FET:ZXMN2A01F +Transistor_FET:ZXMN2A14F +Transistor_FET:ZXMN2B01F +Transistor_FET:ZXMN2B14FH +Transistor_FET:ZXMN2F30FH +Transistor_FET:ZXMN2F34FH +Transistor_FET:ZXMN3A01F +Transistor_FET:ZXMN3A14F +Transistor_FET:ZXMN3B01F +Transistor_FET:ZXMN3B14F +Transistor_FET:ZXMN3F30FH +Transistor_FET:ZXMN6A07F +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 +Transistor_Power_Module:A2C25S12M3-F +Transistor_Power_Module:A2C35S12M3 +Transistor_Power_Module:A2C35S12M3-F +Transistor_Power_Module:A2C50S65M2 +Transistor_Power_Module:A2C50S65M2-F +Transistor_Power_Module:FP10R06W1E3 +Transistor_Power_Module:FP15R06W1E3 +Transistor_Power_Module:FP15R12W2T4 +Transistor_Power_Module:FP25R12W2T4 +Transistor_Power_Module:FP25R12W2T4P +Transistor_Power_Module:FP35R12W2T4 +Transistor_Power_Module:FP35R12W2T4P +Transistor_Power_Module:FP50R06W2E3 +Transistor_Power_Module:FS75R07N2E4 +Transistor_Power_Module:MG12100W-XN2MM +Transistor_Power_Module:MG1215H-XBN2MM +Transistor_Power_Module:MG1225H-XBN2MM +Transistor_Power_Module:MG1225H-XN2MM +Transistor_Power_Module:MG1240H-XBN2MM +Transistor_Power_Module:MG1250H-XN2MM +Transistor_Power_Module:MG1250W-XBN2MM +Transistor_Power_Module:MG1275W-XBN2MM +Transistor_Power_Module:MG1275W-XN2MM +Transistor_Power_Module:STGIPS10C60-H +Transistor_Power_Module:STGIPS10K60A +Transistor_Power_Module:STGIPS10K60A2 +Transistor_Power_Module:STGIPS10K60T +Transistor_Power_Module:STGIPS14K60 +Transistor_Power_Module:STGIPS14K60T +Transistor_Power_Module:STGIPS20K60 +Triac_Thyristor:BT136-500 +Triac_Thyristor:BT136-600 +Triac_Thyristor:BT136-800 +Triac_Thyristor:BT138-600 +Triac_Thyristor:BT138-800 +Triac_Thyristor:BT139-600 +Triac_Thyristor:BT169B +Triac_Thyristor:BT169D +Triac_Thyristor:BT169G +Triac_Thyristor:BTA16-600B +Triac_Thyristor:BTA16-600BW +Triac_Thyristor:BTA16-600C +Triac_Thyristor:BTA16-600CW +Triac_Thyristor:BTA16-600SW +Triac_Thyristor:BTA16-800B +Triac_Thyristor:BTA16-800BW +Triac_Thyristor:BTA16-800C +Triac_Thyristor:BTA16-800CW +Triac_Thyristor:BTA16-800SW +Triac_Thyristor:BTB16-600B +Triac_Thyristor:BTB16-600BW +Triac_Thyristor:BTB16-600C +Triac_Thyristor:BTB16-600CW +Triac_Thyristor:BTB16-600SW +Triac_Thyristor:BTB16-800B +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 +Triac_Thyristor:TIC206 +Triac_Thyristor:TIC216 +Triac_Thyristor:TIC226 +Triac_Thyristor:X0202MN +Triac_Thyristor:X0202NN +Triac_Thyristor:Z0103MN +Triac_Thyristor:Z0103NN +Triac_Thyristor:Z0107MN +Triac_Thyristor:Z0107NN +Triac_Thyristor:Z0109MN +Triac_Thyristor:Z0109NN +Triac_Thyristor:Z0110MN +Triac_Thyristor:Z0110NN +Valve:6AK8 +Valve:9AK8 +Valve:CK548DX +Valve:CK6418 +Valve:EABC80 +Valve:EC92 +Valve:ECC81 +Valve:ECC83 +Valve:ECC88 +Valve:ECH81 +Valve:ECL82 +Valve:ECL86 +Valve:EF80 +Valve:EF83 +Valve:EF85 +Valve:EF86 +Valve:EL34 +Valve:EL84 +Valve:EM84 +Valve:JAN6418 +Valve:NOS-6418 +Valve:PABC80 +Valve:STABI +Valve:UABC80 +Video:AD725 +Video:AD9708AR +Video:AD9891 +Video:AD9895 +Video:AD9984AKST +Video:ADA4430-1WYRTZ +Video:ADA4430-1YKSZ +Video:ADV7280xCP +Video:ADV7390BCPZ +Video:ADV7391BCPZ +Video:AV9173 +Video:CX7930 +Video:CXD3400N +Video:HD63484 +Video:HD63484_PLCC +Video:ICX415AQ +Video:ISL59885 +Video:LM1881 +Video:MAX310 +Video:MAX311 +Video:MB88303P +Video:S178 +Video:SAA7182 +Video:SI582 +Video:TDA1950 +Video:TDA1950F +Video:TDA2593 +Video:TDA7260 +Video:TDA8501 +Video:TDA8702 +Video:TDA8702T +Video:TDA8772 +Video:TDA9500 +Video:TDA9503 +Video:TDA9513 +Video:TEA2014 +Video:TEA5115 +Video:TFP410PAP diff --git a/rector.php b/rector.php index 94ade3df..936b447e 100644 --- a/rector.php +++ b/rector.php @@ -2,18 +2,76 @@ declare(strict_types=1); +use Rector\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector; use Rector\CodingStyle\Rector\FuncCall\CountArrayToEmptyArrayComparisonRector; use Rector\Config\RectorConfig; use Rector\Doctrine\Set\DoctrineSetList; -use Rector\PHPUnit\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector; -use Rector\PHPUnit\Set\PHPUnitLevelSetList; +use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector; +use Rector\PHPUnit\CodeQuality\Rector\MethodCall\AssertEmptyNullableObjectToAssertInstanceofRector; use Rector\PHPUnit\Set\PHPUnitSetList; use Rector\Set\ValueObject\LevelSetList; use Rector\Set\ValueObject\SetList; -use Rector\Symfony\Set\SymfonyLevelSetList; +use Rector\Symfony\CodeQuality\Rector\Class_\EventListenerToEventSubscriberRector; +use Rector\Symfony\CodeQuality\Rector\ClassMethod\ActionSuffixRemoverRector; +use Rector\Symfony\CodeQuality\Rector\MethodCall\LiteralGetToRequestClassConstantRector; use Rector\Symfony\Set\SymfonySetList; use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector; +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'); @@ -44,20 +102,37 @@ return static function (RectorConfig $rectorConfig): void { LevelSetList::UP_TO_PHP_81, //Symfony rules - SymfonyLevelSetList::UP_TO_SYMFONY_62, SymfonySetList::SYMFONY_CODE_QUALITY, + SymfonySetList::SYMFONY_64, //Doctrine rules DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES, DoctrineSetList::DOCTRINE_CODE_QUALITY, //PHPUnit rules - PHPUnitLevelSetList::UP_TO_PHPUNIT_90, PHPUnitSetList::PHPUNIT_CODE_QUALITY, + PHPUnitSetList::PHPUNIT_90, ]); $rectorConfig->skip([ - AddDoesNotPerformAssertionToNonAssertingTestRector::class, CountArrayToEmptyArrayComparisonRector::class, + //Leave our !== null checks alone + FlipTypeControlToUseExclusiveTypeRector::class, + //Leave our PartList TableAction alone + ActionSuffixRemoverRector::class, + //We declare event listeners via attributes, therefore no need to migrate them to subscribers + EventListenerToEventSubscriberRector::class, + PreferPHPUnitThisCallRector::class, + //Do not replace 'GET' with class constant, + LiteralGetToRequestClassConstantRector::class, + ]); + + //Do not apply rules to Symfony own files + $rectorConfig->skip([ + __DIR__ . '/public/index.php', + __DIR__ . '/src/Kernel.php', + __DIR__ . '/config/preload.php', + __DIR__ . '/config/bundles.php', ]); }; +*/ diff --git a/src/ApiPlatform/DocumentedAPIProperties/DocumentedAPIProperty.php b/src/ApiPlatform/DocumentedAPIProperties/DocumentedAPIProperty.php new file mode 100644 index 00000000..57d275be --- /dev/null +++ b/src/ApiPlatform/DocumentedAPIProperties/DocumentedAPIProperty.php @@ -0,0 +1,120 @@ +. + */ + +declare(strict_types=1); + + +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. + * This is useful for adding properties to the API documentation, that are not existing in the entity class itself, + * but get added by a normalizer. + */ +#[\Attribute(\Attribute::TARGET_CLASS| \Attribute::IS_REPEATABLE)] +final class DocumentedAPIProperty +{ + public function __construct( + /** + * @param string $schemaName The name of the schema to add the property to (e.g. "Part-Read") + */ + public readonly string $schemaName, + /** + * @var string $property The name of the property to add to the schema + */ + public readonly string $property, + public readonly string $type = 'string', + public readonly bool $nullable = true, + /** + * @var string $description The description of the property + */ + public readonly ?string $description = null, + /** + * @var bool True if the property is readable, false otherwise + */ + public readonly bool $readable = true, + /** + * @var bool True if the property is writable, false otherwise + */ + public readonly bool $writeable = false, + /** + * @var string|null The deprecation reason of the property + */ + public readonly ?string $deprecationReason = null, + /** @var mixed The default value of this property */ + public readonly mixed $default = null, + public readonly mixed $example = null, + ) + { + } + + 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 00000000..2ffb9179 --- /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 00000000..3157cbf3 --- /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/ErrorHandler.php b/src/ApiPlatform/ErrorHandler.php new file mode 100644 index 00000000..7704347d --- /dev/null +++ b/src/ApiPlatform/ErrorHandler.php @@ -0,0 +1,75 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform; + +use ApiPlatform\Metadata\Operation; +use ApiPlatform\State\ProviderInterface; +use Doctrine\ORM\ORMInvalidArgumentException; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; +use ApiPlatform\State\ApiResource\Error; +use Symfony\Component\DependencyInjection\Attribute\Autowire; + +/** + * This class adds a custom error if the user tries to create a new entity through a relation, and suggests to do reference it through an IRI instead. + * This class decorates the default error handler of API Platform. + */ +#[AsDecorator('api_platform.state.error_provider')] +final class ErrorHandler implements ProviderInterface +{ + public function __construct(private readonly ProviderInterface $decorated, #[Autowire('%kernel.debug%')] private readonly bool $debug) + { + + } + + public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null + { + $request = $context['request']; + $format = $request->getRequestFormat(); + $exception = $request->attributes->get('exception'); + + //Check if the exception is a ORM InvalidArgument exception and complains about a not-persisted entity through relation + if ($exception instanceof ORMInvalidArgumentException && str_contains($exception->getMessage(), 'A new entity was found through the relationship')) { + //Extract the entity class and property name from the exception message + $matches = []; + preg_match('/A new entity was found through the relationship \'(?.*)\'/i', $exception->getMessage(), $matches); + + $property = $matches['property'] ?? "unknown"; + + //Create a new error response + $error = Error::createFromException($exception, 400); + + //Return the error response + $detail = "You tried to create a new entity through the relation '$property', but this is not allowed. Please create the entity first and then reference it through an IRI!"; + //If we are in debug mode, add the exception message to the error response + if ($this->debug) { + $detail .= " Original exception message: " . $exception->getMessage(); + } + $error->setDetail($detail); + return $error; + } + + + return $this->decorated->provide($operation, $uriVariables, $context); + } +} \ No newline at end of file diff --git a/src/ApiPlatform/Filter/EntityFilter.php b/src/ApiPlatform/Filter/EntityFilter.php new file mode 100644 index 00000000..85bc3833 --- /dev/null +++ b/src/ApiPlatform/Filter/EntityFilter.php @@ -0,0 +1,84 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\Filter; + +use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Operation; +use Doctrine\ORM\QueryBuilder; +use Doctrine\Persistence\ManagerRegistry; +use Psr\Log\LoggerInterface; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; + +class EntityFilter extends AbstractFilter +{ + + public function __construct( + ManagerRegistry $managerRegistry, + private readonly EntityFilterHelper $filter_helper, + ?LoggerInterface $logger = null, + ?array $properties = null, + ?NameConverterInterface $nameConverter = null + ) { + parent::__construct($managerRegistry, $logger, $properties, $nameConverter); + } + + protected function filterProperty( + string $property, + $value, + QueryBuilder $queryBuilder, + QueryNameGeneratorInterface $queryNameGenerator, + string $resourceClass, + ?Operation $operation = null, + array $context = [] + ): void { + if ( + !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyMapped($property, $resourceClass, true) + ) { + return; + } + + $metadata = $this->getClassMetadata($resourceClass); + $target_class = $metadata->getAssociationTargetClass($property); + //If it is not an association we can not filter the property + if (!$target_class) { + return; + } + + $elements = $this->filter_helper->valueToEntityArray($value, $target_class); + + $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters + $queryBuilder + ->andWhere(sprintf('o.%s IN (:%s)', $property, $parameterName)) + ->setParameter($parameterName, $elements); + } + + + + public function getDescription(string $resourceClass): array + { + return $this->filter_helper->getDescription($this->properties); + } +} \ No newline at end of file diff --git a/src/ApiPlatform/Filter/EntityFilterHelper.php b/src/ApiPlatform/Filter/EntityFilterHelper.php new file mode 100644 index 00000000..45e04fde --- /dev/null +++ b/src/ApiPlatform/Filter/EntityFilterHelper.php @@ -0,0 +1,99 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\Filter; + +use App\Entity\Base\AbstractStructuralDBElement; +use App\Services\Trees\NodesListBuilder; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\PropertyInfo\Type; + +class EntityFilterHelper +{ + public function __construct( + private readonly NodesListBuilder $nodesListBuilder, + private readonly EntityManagerInterface $entityManager) + { + + } + + public function valueToEntityArray(string $value, string $target_class): array + { + //Convert value to IDs: + $elements = []; + + //Split the given value by comm + foreach (explode(',', $value) as $id) { + if (trim($id) === '') { + continue; + } + + //Check if the given value ends with a plus, then we want to include all direct children + $include_children = false; + $include_recursive = false; + if (str_ends_with($id, '++')) { //Plus Plus means include all children recursively + $id = substr($id, 0, -2); + $include_recursive = true; + } elseif (str_ends_with($id, '+')) { + $id = substr($id, 0, -1); + $include_children = true; + } + + //Get a (shallow) reference to the entitity + $element = $this->entityManager->getReference($target_class, (int) $id); + $elements[] = $element; + + //If $element is not structural we are done + if (!is_a($element, AbstractStructuralDBElement::class)) { + continue; + } + + //Get the recursive list of children + if ($include_recursive) { + $elements = array_merge($elements, $this->nodesListBuilder->getChildrenFlatList($element)); + } elseif ($include_children) { + $elements = array_merge($elements, $element->getChildren()->toArray()); + } + } + + return $elements; + } + + public function getDescription(array $properties): array + { + if ($properties === []) { + return []; + } + + $description = []; + foreach (array_keys($properties) as $property) { + $description[(string)$property] = [ + 'property' => $property, + 'type' => Type::BUILTIN_TYPE_STRING, + 'required' => false, + 'description' => 'Filter using a comma seperated list of element IDs. Use + to include all direct children and ++ to include all children recursively.', + ]; + } + return $description; + } +} \ No newline at end of file diff --git a/src/ApiPlatform/Filter/LikeFilter.php b/src/ApiPlatform/Filter/LikeFilter.php new file mode 100644 index 00000000..a8e96eb9 --- /dev/null +++ b/src/ApiPlatform/Filter/LikeFilter.php @@ -0,0 +1,74 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\Filter; + +use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Operation; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\PropertyInfo\Type; + +final class LikeFilter extends AbstractFilter +{ + + protected function filterProperty( + string $property, + $value, + QueryBuilder $queryBuilder, + QueryNameGeneratorInterface $queryNameGenerator, + string $resourceClass, + ?Operation $operation = null, + array $context = [] + ): void { + // Otherwise filter is applied to order and page as well + if ( + !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyMapped($property, $resourceClass) + ) { + return; + } + $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters + $queryBuilder + ->andWhere(sprintf('ILIKE(o.%s, :%s) = TRUE', $property, $parameterName)) + ->setParameter($parameterName, $value); + } + + public function getDescription(string $resourceClass): array + { + if (!$this->properties) { + return []; + } + + $description = []; + foreach (array_keys($this->properties) as $property) { + $description[(string)$property] = [ + 'property' => $property, + 'type' => Type::BUILTIN_TYPE_STRING, + 'required' => false, + 'description' => 'Filter 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', + ]; + } + return $description; + } +} \ No newline at end of file diff --git a/src/ApiPlatform/Filter/PartStoragelocationFilter.php b/src/ApiPlatform/Filter/PartStoragelocationFilter.php new file mode 100644 index 00000000..4d0ad2df --- /dev/null +++ b/src/ApiPlatform/Filter/PartStoragelocationFilter.php @@ -0,0 +1,79 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\Filter; + +use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Operation; +use App\Entity\Parts\StorageLocation; +use Doctrine\ORM\QueryBuilder; +use Doctrine\Persistence\ManagerRegistry; +use Psr\Log\LoggerInterface; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; + +class PartStoragelocationFilter extends AbstractFilter +{ + + public function __construct( + ManagerRegistry $managerRegistry, + private readonly EntityFilterHelper $filter_helper, + ?LoggerInterface $logger = null, + ?array $properties = null, + ?NameConverterInterface $nameConverter = null + ) { + parent::__construct($managerRegistry, $logger, $properties, $nameConverter); + } + + protected function filterProperty( + string $property, + $value, + QueryBuilder $queryBuilder, + QueryNameGeneratorInterface $queryNameGenerator, + string $resourceClass, + ?Operation $operation = null, + array $context = [] + ): void { + //Do not check for mapping here, as we are using a virtual property + if ( + !$this->isPropertyEnabled($property, $resourceClass) + ) { + return; + } + + $elements = $this->filter_helper->valueToEntityArray($value, StorageLocation::class); + + $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters + $queryBuilder + ->leftJoin('o.partLots', 'partLots') + ->andWhere(sprintf('partLots.storage_location IN (:%s)', $parameterName)) + ->setParameter($parameterName, $elements); + } + + + + public function getDescription(string $resourceClass): array + { + return $this->filter_helper->getDescription($this->properties); + } +} \ No newline at end of file diff --git a/src/ApiPlatform/Filter/TagFilter.php b/src/ApiPlatform/Filter/TagFilter.php new file mode 100644 index 00000000..98648ee9 --- /dev/null +++ b/src/ApiPlatform/Filter/TagFilter.php @@ -0,0 +1,96 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\Filter; + +use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Operation; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\PropertyInfo\Type; + +/** + * Due to their nature, tags are stored in a single string, separated by commas, which requires some more complex search logic. + * This filter allows to easily search for tags in a part entity. + */ +final class TagFilter extends AbstractFilter +{ + + protected function filterProperty( + string $property, + $value, + QueryBuilder $queryBuilder, + QueryNameGeneratorInterface $queryNameGenerator, + string $resourceClass, + ?Operation $operation = null, + array $context = [] + ): void { + // Ignore filter if property is not enabled or mapped + if ( + !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyMapped($property, $resourceClass) + ) { + return; + } + + //Escape any %, _ or \ in the tag + $value = addcslashes($value, '%_\\'); + + $tag_identifier_prefix = $queryNameGenerator->generateParameterName($property); + + $expr = $queryBuilder->expr(); + + $tmp = $expr->orX( + 'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_1) = TRUE', + 'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_2) = TRUE', + 'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_3) = TRUE', + 'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_4) = TRUE', + ); + + $queryBuilder->andWhere($tmp); + + //Set the parameters for the LIKE expression, in each variation of the tag (so with a comma, at the end, at the beginning, and on both ends, and equaling the tag) + $queryBuilder->setParameter($tag_identifier_prefix . '_1', '%,' . $value . ',%'); + $queryBuilder->setParameter($tag_identifier_prefix . '_2', '%,' . $value); + $queryBuilder->setParameter($tag_identifier_prefix . '_3', $value . ',%'); + $queryBuilder->setParameter($tag_identifier_prefix . '_4', $value); + } + + public function getDescription(string $resourceClass): array + { + if (!$this->properties) { + return []; + } + + $description = []; + foreach (array_keys($this->properties) as $property) { + $description[(string)$property] = [ + 'property' => $property, + 'type' => Type::BUILTIN_TYPE_STRING, + 'required' => false, + 'description' => 'Filter for tags of a part', + ]; + } + return $description; + } +} \ No newline at end of file diff --git a/src/ApiPlatform/FixInheritanceMappingMetadataFacory.php b/src/ApiPlatform/FixInheritanceMappingMetadataFacory.php new file mode 100644 index 00000000..c65e57a0 --- /dev/null +++ b/src/ApiPlatform/FixInheritanceMappingMetadataFacory.php @@ -0,0 +1,72 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform; + +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use App\Entity\Attachments\Attachment; +use App\Entity\Parameters\AbstractParameter; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; + +/** + * API Platform has problems with single table inheritance, as it assumes that they all have different endpoints. + * This decorator fixes this problem by using the parent class for the metadata collection. + */ +#[AsDecorator('api_platform.metadata.resource.metadata_collection_factory')] +class FixInheritanceMappingMetadataFacory implements ResourceMetadataCollectionFactoryInterface +{ + private const SINGLE_INHERITANCE_ENTITY_CLASSES = [ + Attachment::class, + AbstractParameter::class, + ]; + + private array $cache = []; + + public function __construct(private readonly ResourceMetadataCollectionFactoryInterface $decorated) + { + } + + public function create(string $resourceClass): ResourceMetadataCollection + { + //If we already have a cached value, we can return it + if (isset($this->cache[$resourceClass])) { + return $this->decorated->create($this->cache[$resourceClass]); + } + + //Check if the resourceClass is a single inheritance class, then we can use the parent class to access it + foreach (self::SINGLE_INHERITANCE_ENTITY_CLASSES as $class) { + if (is_a($resourceClass, $class, true)) { + $this->cache[$resourceClass] = $class; + break; + } + } + + //If it was not found in the list of single inheritance classes, we can use the original class + if (!isset($this->cache[$resourceClass])) { + $this->cache[$resourceClass] = $resourceClass; + } + + return $this->decorated->create($this->cache[$resourceClass] ?? $resourceClass); + } +} \ No newline at end of file diff --git a/src/ApiPlatform/HandleAttachmentsUploadsProcessor.php b/src/ApiPlatform/HandleAttachmentsUploadsProcessor.php new file mode 100644 index 00000000..b5149442 --- /dev/null +++ b/src/ApiPlatform/HandleAttachmentsUploadsProcessor.php @@ -0,0 +1,67 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform; + +use ApiPlatform\Metadata\DeleteOperationInterface; +use ApiPlatform\Metadata\Operation; +use ApiPlatform\State\ProcessorInterface; +use App\Entity\Attachments\Attachment; +use App\Services\Attachments\AttachmentSubmitHandler; +use Symfony\Component\DependencyInjection\Attribute\Autowire; + +/** + * This state processor handles the upload property set on the deserialized attachment entity and + * calls the upload handler service to handle the upload. + */ +final class HandleAttachmentsUploadsProcessor implements ProcessorInterface +{ + public function __construct( + #[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')] + private readonly ProcessorInterface $persistProcessor, + #[Autowire(service: 'api_platform.doctrine.orm.state.remove_processor')] + private readonly ProcessorInterface $removeProcessor, + private readonly AttachmentSubmitHandler $attachmentSubmitHandler + ) { + + } + + public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed + { + if ($operation instanceof DeleteOperationInterface) { + return $this->removeProcessor->process($data, $operation, $uriVariables, $context); + } + + //Check if the attachment has any upload data we need to handle + //This have to happen before the persist processor is called, because the changes on the entity must be saved! + if ($data instanceof Attachment && $data->getUpload()) { + $upload = $data->getUpload(); + //Reset the upload data + $data->setUpload(null); + + $this->attachmentSubmitHandler->handleUpload($data, $upload); + } + + return $this->persistProcessor->process($data, $operation, $uriVariables, $context); + } +} \ No newline at end of file diff --git a/src/ApiPlatform/NormalizePropertyNameCollectionFactory.php b/src/ApiPlatform/NormalizePropertyNameCollectionFactory.php new file mode 100644 index 00000000..c6a8220e --- /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/ApiPlatform/OpenApiFactoryDecorator.php b/src/ApiPlatform/OpenApiFactoryDecorator.php new file mode 100644 index 00000000..af213e14 --- /dev/null +++ b/src/ApiPlatform/OpenApiFactoryDecorator.php @@ -0,0 +1,50 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform; + +use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface; +use ApiPlatform\OpenApi\Model\SecurityScheme; +use ApiPlatform\OpenApi\OpenApi; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; + +#[AsDecorator('api_platform.openapi.factory')] +class OpenApiFactoryDecorator implements OpenApiFactoryInterface +{ + public function __construct(private readonly OpenApiFactoryInterface $decorated) + { + } + + public function __invoke(array $context = []): OpenApi + { + $openApi = $this->decorated->__invoke($context); + $securitySchemes = $openApi->getComponents()->getSecuritySchemes() ?: new \ArrayObject(); + $securitySchemes['access_token'] = new SecurityScheme( + type: 'http', + description: 'Use an API token to authenticate', + name: 'Authorization', + scheme: 'bearer', + ); + return $openApi; + } +} \ No newline at end of file diff --git a/src/ApiResource/PartDBInfo.php b/src/ApiResource/PartDBInfo.php new file mode 100644 index 00000000..25aed05e --- /dev/null +++ b/src/ApiResource/PartDBInfo.php @@ -0,0 +1,67 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiResource; + +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\State\PartDBInfoProvider; + +/** + * This class is used to provide various information about the system. + */ +#[ApiResource( + uriTemplate: '/info.{_format}', + description: 'Basic information about Part-DB like version, title, etc.', + operations: [new Get(openapi: new Operation(summary: 'Get basic information about the installed Part-DB instance.'))], + provider: PartDBInfoProvider::class +)] +#[ApiFilter(PropertyFilter::class)] +class PartDBInfo +{ + public function __construct( + /** The installed Part-DB version */ + public readonly string $version, + /** The Git branch name of the Part-DB version (or null, if not installed via git) */ + public readonly string|null $git_branch, + /** The Git branch commit of the Part-DB version (or null, if not installed via git) */ + public readonly string|null $git_commit, + /** The name of this Part-DB instance */ + public readonly string $title, + /** The banner, shown on homepage (markdown encoded) */ + public readonly string $banner, + /** The configured default URI for Part-DB */ + public readonly string $default_uri, + /** The global timezone of this Part-DB */ + public readonly string $global_timezone, + /** The base currency of Part-DB, used as internal representation of monetary values */ + public readonly string $base_currency, + /** The configured default language of Part-DB */ + public readonly string $global_locale, + ) { + + } +} \ No newline at end of file diff --git a/src/Command/Attachments/CleanAttachmentsCommand.php b/src/Command/Attachments/CleanAttachmentsCommand.php index e9ffd286..59bc99ee 100644 --- a/src/Command/Attachments/CleanAttachmentsCommand.php +++ b/src/Command/Attachments/CleanAttachmentsCommand.php @@ -73,6 +73,9 @@ class CleanAttachmentsCommand extends Command //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 00000000..34deef0e --- /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 00000000..7f6550f0 --- /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/BackupCommand.php b/src/Command/BackupCommand.php index ef7d038f..085c552a 100644 --- a/src/Command/BackupCommand.php +++ b/src/Command/BackupCommand.php @@ -4,8 +4,10 @@ declare(strict_types=1); namespace App\Command; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Spatie\DbDumper\Databases\PostgreSql; use Symfony\Component\Console\Attribute\AsCommand; -use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\ORM\EntityManagerInterface; use PhpZip\Constants\ZipCompressionMethod; @@ -50,6 +52,7 @@ class BackupCommand extends Command $backup_attachments = $input->getOption('attachments'); $backup_config = $input->getOption('config'); $backup_full = $input->getOption('full'); + $overwrite = $input->getOption('overwrite'); if ($backup_full) { $backup_database = true; @@ -68,7 +71,9 @@ class BackupCommand extends Command //Check if the file already exists //Then ask the user, if he wants to overwrite the file - if (file_exists($output_filepath) && !$io->confirm('The file '.realpath($output_filepath).' already exists. Do you want to overwrite it?', false)) { + if (!$overwrite + && file_exists($output_filepath) + && !$io->confirm('The file '.realpath($output_filepath).' already exists. Do you want to overwrite it?', false)) { $io->error('Backup aborted!'); return Command::FAILURE; } @@ -136,30 +141,42 @@ class BackupCommand extends Command } } + private function runSQLDumper(DbDumper $dumper, ZipFile $zip, array $connectionParams): void + { + $this->configureDumper($connectionParams, $dumper); + + $tmp_file = tempnam(sys_get_temp_dir(), 'partdb_sql_dump'); + + $dumper->dumpToFile($tmp_file); + $zip->addFile($tmp_file, 'database.sql'); + } + protected function backupDatabase(ZipFile $zip, SymfonyStyle $io): void { $io->note('Backup database...'); //Determine if we use MySQL or SQLite $connection = $this->entityManager->getConnection(); - if ($connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) { + $params = $connection->getParams(); + $platform = $connection->getDatabasePlatform(); + if ($platform instanceof AbstractMySQLPlatform) { try { $io->note('MySQL database detected. Dump DB to SQL using mysqldump...'); - $params = $connection->getParams(); - $dumper = MySql::create(); - $this->configureDumper($params, $dumper); - - $tmp_file = tempnam(sys_get_temp_dir(), 'partdb_sql_dump'); - - $dumper->dumpToFile($tmp_file); - $zip->addFile($tmp_file, 'mysql_dump.sql'); + $this->runSQLDumper(MySql::create(), $zip, $params); } catch (\Exception $e) { $io->error('Could not dump database: '.$e->getMessage()); $io->error('This can maybe be fixed by installing the mysqldump binary and adding it to the PATH variable!'); } - } elseif ($connection->getDatabasePlatform() instanceof SqlitePlatform) { + } elseif ($platform instanceof PostgreSQLPlatform) { + try { + $io->note('PostgreSQL database detected. Dump DB to SQL using pg_dump...'); + $this->runSQLDumper(PostgreSql::create(), $zip, $params); + } catch (\Exception $e) { + $io->error('Could not dump database: '.$e->getMessage()); + $io->error('This can maybe be fixed by installing the pg_dump binary and adding it to the PATH variable!'); + } + } elseif ($platform instanceof SQLitePlatform) { $io->note('SQLite database detected. Copy DB file to ZIP...'); - $params = $connection->getParams(); $zip->addFile($params['path'], 'var/app.db'); } else { $io->error('Unknown database platform. Could not backup database!'); diff --git a/src/Command/CheckRequirementsCommand.php b/src/Command/CheckRequirementsCommand.php index 068147e2..f9080c42 100644 --- a/src/Command/CheckRequirementsCommand.php +++ b/src/Command/CheckRequirementsCommand.php @@ -69,13 +69,24 @@ class CheckRequirementsCommand extends Command 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.'); } + //Checking 32-bit system + if (PHP_INT_SIZE === 4) { + $io->warning('You are using a 32-bit system. You will have problems with working with dates after the year 2038, therefore a 64-bit system is recommended.'); + } elseif (PHP_INT_SIZE === 8) { //@phpstan-ignore-line //PHP_INT_SIZE is always 4 or 8 + if (!$only_issues) { + $io->success('You are using a 64-bit system.'); + } + } else { + $io->warning(' areYou using a system with an unknown bit size. That is interesting xD'); + } + //Check if opcache is enabled if ($io->isVerbose()) { $io->comment('Checking Opcache...'); diff --git a/src/Command/Currencies/UpdateExchangeRatesCommand.php b/src/Command/Currencies/UpdateExchangeRatesCommand.php index 0f3eb11f..2c1f5f92 100644 --- a/src/Command/Currencies/UpdateExchangeRatesCommand.php +++ b/src/Command/Currencies/UpdateExchangeRatesCommand.php @@ -22,6 +22,7 @@ declare(strict_types=1); 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 @@ use function strlen; #[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 @@ class UpdateExchangeRatesCommand extends Command $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/LoadFixturesCommand.php b/src/Command/LoadFixturesCommand.php new file mode 100644 index 00000000..d01d19c3 --- /dev/null +++ b/src/Command/LoadFixturesCommand.php @@ -0,0 +1,73 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Command; + +use App\Doctrine\Purger\ResetAutoIncrementPurgerFactory; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * This command does basically the same as doctrine:fixtures:load, but it purges the database before loading the fixtures. + * It does so in another transaction, so we can modify the purger to reset the autoincrement, which would not be possible + * because the implicit commit otherwise. + */ +#[AsCommand(name: 'partdb:fixtures:load', description: 'Load test fixtures into the database and allows to reset the autoincrement before loading the fixtures.', hidden: true)] +class LoadFixturesCommand extends Command +{ + public function __construct(private readonly EntityManagerInterface $entityManager) + { + parent::__construct(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $ui = new SymfonyStyle($input, $output); + + $ui->warning('This command is for development and testing purposes only. It will purge the database and load fixtures afterwards. Do not use in production!'); + + if (! $ui->confirm(sprintf('Careful, database "%s" will be purged. Do you want to continue?', $this->entityManager->getConnection()->getDatabase()), ! $input->isInteractive())) { + return 0; + } + + $factory = new ResetAutoIncrementPurgerFactory(); + $purger = $factory->createForEntityManager(null, $this->entityManager); + + $purger->purge(); + + //Afterwards run the load fixtures command as normal, but with the --append option + $new_input = new ArrayInput([ + 'command' => 'doctrine:fixtures:load', + '--append' => true, + ]); + + $returnCode = $this->getApplication()?->doRun($new_input, $output); + + return $returnCode ?? Command::FAILURE; + } +} \ No newline at end of file diff --git a/src/Command/Logs/ShowEventLogCommand.php b/src/Command/Logs/ShowEventLogCommand.php index 2d11b359..505b1275 100644 --- a/src/Command/Logs/ShowEventLogCommand.php +++ b/src/Command/Logs/ShowEventLogCommand.php @@ -65,12 +65,12 @@ class ShowEventLogCommand extends Command $max_page = (int) ceil($total_count / $limit); if ($page > $max_page && $max_page > 0) { - $io->error("There is no page ${page}! The maximum page is ${max_page}."); + $io->error("There is no page $page! The maximum page is $max_page."); return Command::FAILURE; } - $io->note("There are a total of ${total_count} log entries in the DB."); + $io->note("There are a total of $total_count log entries in the DB."); $continue = true; while ($continue && $page <= $max_page) { @@ -105,7 +105,7 @@ class ShowEventLogCommand extends Command $entries = $this->repo->getLogsOrderedByTimestamp($sorting, $limit, $offset); $table = new Table($output); - $table->setHeaderTitle("Page ${page} / ${max_page}"); + $table->setHeaderTitle("Page $page / $max_page"); $headers = ['ID', 'Timestamp', 'Type', 'User', 'Target Type', 'Target']; if ($showExtra) { $headers[] = 'Extra data'; diff --git a/src/Command/Migrations/ConvertBBCodeCommand.php b/src/Command/Migrations/ConvertBBCodeCommand.php index 2297cbdd..201263ff 100644 --- a/src/Command/Migrations/ConvertBBCodeCommand.php +++ b/src/Command/Migrations/ConvertBBCodeCommand.php @@ -30,7 +30,7 @@ use App\Entity\Parts\Category; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\UserSystem\Group; @@ -79,13 +79,14 @@ class ConvertBBCodeCommand extends Command /** * Returns a list which entities and which properties need to be checked. + * @return array, string[]> */ protected function getTargetsLists(): array { return [ Part::class => ['description', 'comment'], AttachmentType::class => ['comment'], - Storelocation::class => ['comment'], + StorageLocation::class => ['comment'], Project::class => ['comment'], Category::class => ['comment'], Manufacturer::class => ['comment'], @@ -109,7 +110,6 @@ class ConvertBBCodeCommand extends Command $class )); //Determine which entities of this type we need to modify - /** @var EntityRepository $repo */ $repo = $this->em->getRepository($class); $qb = $repo->createQueryBuilder('e') ->select('e'); diff --git a/src/Command/Migrations/ImportPartKeeprCommand.php b/src/Command/Migrations/ImportPartKeeprCommand.php index 98272440..429f018d 100644 --- a/src/Command/Migrations/ImportPartKeeprCommand.php +++ b/src/Command/Migrations/ImportPartKeeprCommand.php @@ -44,7 +44,7 @@ class ImportPartKeeprCommand extends Command 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 @@ class ImportPartKeeprCommand extends Command $count = $this->datastructureImporter->importPartUnits($data); $io->success('Imported '.$count.' measurement units.'); + //Import the custom states + $io->info('Importing custom states...'); + $count = $this->datastructureImporter->importPartCustomStates($data); + $io->success('Imported '.$count.' custom states.'); + //Import manufacturers $io->info('Importing manufacturers...'); $count = $this->datastructureImporter->importManufacturers($data); diff --git a/src/Command/User/SetPasswordCommand.php b/src/Command/User/SetPasswordCommand.php index 822277f1..30ef867b 100644 --- a/src/Command/User/SetPasswordCommand.php +++ b/src/Command/User/SetPasswordCommand.php @@ -83,6 +83,19 @@ class SetPasswordCommand extends Command while (!$success) { $pw1 = $io->askHidden('Please enter new password:'); + + if ($pw1 === null) { + $io->error('No password entered! Please try again.'); + + //If we are in non-interactive mode, we can not ask again + if (!$input->isInteractive()) { + $io->warning('Non-interactive mode detected. No password can be entered that way! If you are using docker exec, please use -it flag.'); + return Command::FAILURE; + } + + continue; + } + $pw2 = $io->askHidden('Please confirm:'); if ($pw1 !== $pw2) { $io->error('The entered password did not match! Please try again.'); diff --git a/src/Command/User/UpgradePermissionsSchemaCommand.php b/src/Command/User/UpgradePermissionsSchemaCommand.php index 4947fd5c..a53e21a0 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 00753e94..51ff2280 100644 --- a/src/Command/User/UserEnableCommand.php +++ b/src/Command/User/UserEnableCommand.php @@ -35,7 +35,7 @@ use Symfony\Component\Console\Style\SymfonyStyle; #[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 021853bb..27382371 100644 --- a/src/Command/User/UsersPermissionsCommand.php +++ b/src/Command/User/UsersPermissionsCommand.php @@ -46,7 +46,7 @@ class UsersPermissionsCommand extends Command { $this->userRepository = $entityManager->getRepository(User::class); - parent::__construct(self::$defaultName); + parent::__construct(); } protected function configure(): void @@ -206,12 +206,15 @@ class UsersPermissionsCommand extends Command return 'Allow'; } elseif ($permission_value === false) { return 'Disallow'; - } elseif ($permission_value === null && !$inherit) { + } + // Permission value is null by this point + elseif (!$inherit) { return 'Inherit'; - } elseif ($permission_value === null && $inherit) { + } elseif ($inherit) { return 'Disallow (Inherited)'; } + //@phpstan-ignore-next-line This line is never reached, but PHPstorm complains otherwise return '???'; } } diff --git a/src/Configuration/PermissionsConfiguration.php b/src/Configuration/PermissionsConfiguration.php index 307e3794..c069ab24 100644 --- a/src/Configuration/PermissionsConfiguration.php +++ b/src/Configuration/PermissionsConfiguration.php @@ -55,6 +55,7 @@ final class PermissionsConfiguration implements ConfigurationInterface ->scalarNode('name')->end() ->scalarNode('label')->end() ->scalarNode('bit')->end() + ->scalarNode('apiTokenRole')->defaultNull()->end() ->arrayNode('alsoSet') ->beforeNormalization()->castToArray()->end()->scalarPrototype()->end(); diff --git a/src/Controller/AdminPages/AttachmentTypeController.php b/src/Controller/AdminPages/AttachmentTypeController.php index 426e773c..08c10da1 100644 --- a/src/Controller/AdminPages/AttachmentTypeController.php +++ b/src/Controller/AdminPages/AttachmentTypeController.php @@ -34,7 +34,7 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * @see \App\Tests\Controller\AdminPages\AttachmentTypeControllerTest @@ -55,7 +55,7 @@ class AttachmentTypeController extends BaseAdminController return $this->_delete($request, $entity, $recursionHelper); } - #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'attachment_type_edit')] + #[Route(path: '/{id}/edit/{timestamp}', name: 'attachment_type_edit', requirements: ['id' => '\d+'])] #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(AttachmentType $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { diff --git a/src/Controller/AdminPages/BaseAdminController.php b/src/Controller/AdminPages/BaseAdminController.php index 9f43d07d..7c109751 100644 --- a/src/Controller/AdminPages/BaseAdminController.php +++ b/src/Controller/AdminPages/BaseAdminController.php @@ -24,6 +24,8 @@ namespace App\Controller\AdminPages; use App\DataTables\LogDataTable; use App\Entity\Attachments\Attachment; +use App\Entity\Attachments\AttachmentContainingDBElement; +use App\Entity\Attachments\AttachmentUpload; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractPartsContainingDBElement; @@ -32,8 +34,8 @@ use App\Entity\Base\PartsContainingRepositoryInterface; use App\Entity\LabelSystem\LabelProcessMode; use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parameters\AbstractParameter; -use App\Entity\UserSystem\User; use App\Exceptions\AttachmentDownloadException; +use App\Exceptions\TwigModeException; use App\Form\AdminPages\ImportType; use App\Form\AdminPages\MassCreationForm; use App\Repository\AbstractPartsContainingRepository; @@ -51,8 +53,8 @@ use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use Omines\DataTablesBundle\DataTableFactory; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -60,7 +62,8 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Serializer\Exception\UnexpectedValueException; -use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\ConstraintViolationInterface; +use Symfony\Component\Validator\ConstraintViolationListInterface; use Symfony\Contracts\Translation\TranslatorInterface; use function Symfony\Component\Translation\t; @@ -73,15 +76,11 @@ abstract class BaseAdminController extends AbstractController protected string $route_base = ''; protected string $attachment_class = ''; protected ?string $parameter_class = ''; - /** - * @var EventDispatcher|EventDispatcherInterface - */ - protected $eventDispatcher; public function __construct(protected TranslatorInterface $translator, protected UserPasswordHasherInterface $passwordEncoder, protected AttachmentSubmitHandler $attachmentSubmitHandler, protected EventCommentHelper $commentHelper, protected HistoryHelper $historyHelper, protected TimeTravel $timeTravel, - protected DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher, protected LabelExampleElementsGenerator $barcodeExampleGenerator, + protected DataTableFactory $dataTableFactory, protected EventDispatcherInterface $eventDispatcher, protected LabelExampleElementsGenerator $barcodeExampleGenerator, protected LabelGenerator $labelGenerator, protected EntityManagerInterface $entityManager) { if ('' === $this->entity_class || '' === $this->form_class || '' === $this->twig_template || '' === $this->route_base) { @@ -95,7 +94,6 @@ abstract class BaseAdminController extends AbstractController if ('' === $this->parameter_class || ($this->parameter_class && !is_a($this->parameter_class, AbstractParameter::class, true))) { throw new InvalidArgumentException('You have to override the $parameter_class value with a valid Parameter class in your subclass!'); } - $this->eventDispatcher = $eventDispatcher; } protected function revertElementIfNeeded(AbstractDBElement $entity, ?string $timestamp): ?DateTime @@ -175,16 +173,10 @@ abstract class BaseAdminController extends AbstractController $attachments = $form['attachments']; foreach ($attachments as $attachment) { /** @var FormInterface $attachment */ - $options = [ - 'secure_attachment' => $attachment['secureFile']->getData(), - 'download_url' => $attachment['downloadURL']->getData(), - ]; - try { - $this->attachmentSubmitHandler->handleFormSubmit( + $this->attachmentSubmitHandler->handleUpload( $attachment->getData(), - $attachment['file']->getData(), - $options + AttachmentUpload::fromAttachmentForm($attachment) ); } catch (AttachmentDownloadException $attachmentDownloadException) { $this->addFlash( @@ -196,6 +188,11 @@ abstract class BaseAdminController extends AbstractController } } + //Ensure that the master picture is still part of the attachments + if ($entity instanceof AttachmentContainingDBElement && ($entity->getMasterPictureAttachment() !== null && !$entity->getAttachments()->contains($entity->getMasterPictureAttachment()))) { + $entity->setMasterPictureAttachment(null); + } + $this->commentHelper->setMessage($form['log_comment']->getData()); $em->persist($entity); @@ -216,10 +213,14 @@ abstract class BaseAdminController extends AbstractController //Show preview for LabelProfile if needed. if ($entity instanceof LabelProfile) { $example = $this->barcodeExampleGenerator->getElement($entity->getOptions()->getSupportedElement()); - $pdf_data = $this->labelGenerator->generateLabel($entity->getOptions(), $example); + $pdf_data = null; + try { + $pdf_data = $this->labelGenerator->generateLabel($entity->getOptions(), $example); + } catch (TwigModeException $exception) { + $form->get('options')->get('lines')->addError(new FormError($exception->getSafeMessage())); + } } - /** @var AbstractPartsContainingRepository $repo */ $repo = $this->entityManager->getRepository($this->entity_class); return $this->render($this->twig_template, [ @@ -231,6 +232,7 @@ abstract class BaseAdminController extends AbstractController 'timeTravel' => $timeTravel_timestamp, 'repo' => $repo, 'partsContainingElement' => $repo instanceof PartsContainingRepositoryInterface, + 'showParameters' => !($this instanceof PartCustomStateController), ]); } @@ -264,16 +266,11 @@ abstract class BaseAdminController extends AbstractController $attachments = $form['attachments']; foreach ($attachments as $attachment) { /** @var FormInterface $attachment */ - $options = [ - 'secure_attachment' => $attachment['secureFile']->getData(), - 'download_url' => $attachment['downloadURL']->getData(), - ]; try { - $this->attachmentSubmitHandler->handleFormSubmit( + $this->attachmentSubmitHandler->handleUpload( $attachment->getData(), - $attachment['file']->getData(), - $options + AttachmentUpload::fromAttachmentForm($attachment) ); } catch (AttachmentDownloadException $attachmentDownloadException) { $this->addFlash( @@ -284,6 +281,12 @@ abstract class BaseAdminController extends AbstractController ); } } + + //Ensure that the master picture is still part of the attachments + if ($new_entity instanceof AttachmentContainingDBElement && ($new_entity->getMasterPictureAttachment() !== null && !$new_entity->getAttachments()->contains($new_entity->getMasterPictureAttachment()))) { + $new_entity->setMasterPictureAttachment(null); + } + $this->commentHelper->setMessage($form['log_comment']->getData()); $em->persist($new_entity); $em->flush(); @@ -328,8 +331,8 @@ abstract class BaseAdminController extends AbstractController try { $errors = $importer->importFileAndPersistToDB($file, $options); - foreach ($errors as $name => $error) { - foreach ($error as $violation) { + foreach ($errors as $name => ['violations' => $violations]) { + foreach ($violations as $violation) { $this->addFlash('error', $name.': '.$violation->getMessage()); } } @@ -339,6 +342,7 @@ abstract class BaseAdminController extends AbstractController } } + ret: //Mass creation form $mass_creation_form = $this->createForm(MassCreationForm::class, ['entity_class' => $this->entity_class]); $mass_creation_form->handleRequest($request); @@ -351,11 +355,22 @@ abstract class BaseAdminController extends AbstractController $results = $importer->massCreation($data['lines'], $this->entity_class, $data['parent'] ?? null, $errors); //Show errors to user: - foreach ($errors as $error) { - if ($error['entity'] instanceof AbstractStructuralDBElement) { - $this->addFlash('error', $error['entity']->getFullPath().':'.$error['violations']); - } else { //When we don't have a structural element, we can only show the name - $this->addFlash('error', $error['entity']->getName().':'.$error['violations']); + foreach ($errors as ['entity' => $new_entity, 'violations' => $violations]) { + /** @var ConstraintViolationInterface $violation */ + foreach ($violations as $violation) { + if ($new_entity instanceof AbstractStructuralDBElement) { + $this->addFlash('error', $new_entity->getFullPath().':'.$violation->getMessage()); + } else { //When we don't have a structural element, we can only show the name + $this->addFlash('error', $new_entity->getName().':'.$violation->getMessage()); + } + } + } + + //Count how many actual new entities were created (id is null until persisted) + $created_count = 0; + foreach ($results as $result) { + if (null === $result->getID()) { + $created_count++; } } @@ -364,15 +379,25 @@ abstract class BaseAdminController extends AbstractController $em->persist($result); } $em->flush(); + + if (count($results) > 0) { + $this->addFlash('success', t('entity.mass_creation_flash', ['%COUNT%' => $created_count])); + } + + if (count($errors)) { + //Recreate mass creation form, so we get the updated parent list and empty lines + $mass_creation_form = $this->createForm(MassCreationForm::class, ['entity_class' => $this->entity_class]); + } + } - ret: return $this->render($this->twig_template, [ 'entity' => $new_entity, 'form' => $form, 'import_form' => $import_form, 'mass_creation_form' => $mass_creation_form, 'route_base' => $this->route_base, + 'showParameters' => !($this instanceof PartCustomStateController), ]); } @@ -387,7 +412,7 @@ abstract class BaseAdminController extends AbstractController { if ($entity instanceof AbstractPartsContainingDBElement) { /** @var AbstractPartsContainingRepository $repo */ - $repo = $this->entityManager->getRepository($this->entity_class); + $repo = $this->entityManager->getRepository($this->entity_class); //@phpstan-ignore-line if ($repo->getPartsCount($entity) > 0) { $this->addFlash('error', t('entity.delete.must_not_contain_parts', ['%PATH%' => $entity->getFullPath()])); @@ -458,6 +483,11 @@ abstract class BaseAdminController extends AbstractController $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/CategoryController.php b/src/Controller/AdminPages/CategoryController.php index 153d46f3..361df625 100644 --- a/src/Controller/AdminPages/CategoryController.php +++ b/src/Controller/AdminPages/CategoryController.php @@ -33,7 +33,7 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * @see \App\Tests\Controller\AdminPages\CategoryControllerTest @@ -54,7 +54,7 @@ class CategoryController extends BaseAdminController return $this->_delete($request, $entity, $recursionHelper); } - #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'category_edit')] + #[Route(path: '/{id}/edit/{timestamp}', name: 'category_edit', requirements: ['id' => '\d+'])] #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(Category $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { diff --git a/src/Controller/AdminPages/CurrencyController.php b/src/Controller/AdminPages/CurrencyController.php index 18d449fd..4450e157 100644 --- a/src/Controller/AdminPages/CurrencyController.php +++ b/src/Controller/AdminPages/CurrencyController.php @@ -38,7 +38,6 @@ use App\Services\LogSystem\HistoryHelper; use App\Services\LogSystem\TimeTravel; use App\Services\Trees\StructuralElementRecursionHelper; use Doctrine\ORM\EntityManagerInterface; -use Exchanger\Exception\ChainException; use Exchanger\Exception\Exception; use Exchanger\Exception\UnsupportedCurrencyPairException; use Omines\DataTablesBundle\DataTableFactory; @@ -48,7 +47,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use Symfony\Contracts\Translation\TranslatorInterface; /** @@ -124,7 +123,7 @@ class CurrencyController extends BaseAdminController return true; } - #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'currency_edit')] + #[Route(path: '/{id}/edit/{timestamp}', name: 'currency_edit', requirements: ['id' => '\d+'])] #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(Currency $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { diff --git a/src/Controller/AdminPages/FootprintController.php b/src/Controller/AdminPages/FootprintController.php index d1414d2a..3932e939 100644 --- a/src/Controller/AdminPages/FootprintController.php +++ b/src/Controller/AdminPages/FootprintController.php @@ -34,7 +34,7 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * @see \App\Tests\Controller\AdminPages\FootprintControllerTest @@ -55,7 +55,7 @@ class FootprintController extends BaseAdminController return $this->_delete($request, $entity, $recursionHelper); } - #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'footprint_edit')] + #[Route(path: '/{id}/edit/{timestamp}', name: 'footprint_edit', requirements: ['id' => '\d+'])] #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(Footprint $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { diff --git a/src/Controller/AdminPages/LabelProfileController.php b/src/Controller/AdminPages/LabelProfileController.php index ee754436..7e7dfa1c 100644 --- a/src/Controller/AdminPages/LabelProfileController.php +++ b/src/Controller/AdminPages/LabelProfileController.php @@ -22,10 +22,8 @@ declare(strict_types=1); namespace App\Controller\AdminPages; -use App\Entity\Attachments\AttachmentType; use App\Entity\Attachments\LabelAttachment; use App\Entity\LabelSystem\LabelProfile; -use App\Entity\Parameters\AbstractParameter; use App\Form\AdminPages\LabelProfileAdminForm; use App\Services\ImportExportSystem\EntityExporter; use App\Services\ImportExportSystem\EntityImporter; @@ -34,7 +32,7 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * @see \App\Tests\Controller\AdminPages\LabelProfileControllerTest */ @@ -55,7 +53,7 @@ class LabelProfileController extends BaseAdminController return $this->_delete($request, $entity, $recursionHelper); } - #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'label_profile_edit')] + #[Route(path: '/{id}/edit/{timestamp}', name: 'label_profile_edit', requirements: ['id' => '\d+'])] #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(LabelProfile $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { diff --git a/src/Controller/AdminPages/ManufacturerController.php b/src/Controller/AdminPages/ManufacturerController.php index 2a97d3f3..5fc4d4ac 100644 --- a/src/Controller/AdminPages/ManufacturerController.php +++ b/src/Controller/AdminPages/ManufacturerController.php @@ -33,7 +33,7 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * @see \App\Tests\Controller\AdminPages\ManufacturerControllerTest @@ -54,7 +54,7 @@ class ManufacturerController extends BaseAdminController return $this->_delete($request, $entity, $recursionHelper); } - #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'manufacturer_edit')] + #[Route(path: '/{id}/edit/{timestamp}', name: 'manufacturer_edit', requirements: ['id' => '\d+'])] #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(Manufacturer $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { diff --git a/src/Controller/AdminPages/MeasurementUnitController.php b/src/Controller/AdminPages/MeasurementUnitController.php index 993c5dad..59c38ba4 100644 --- a/src/Controller/AdminPages/MeasurementUnitController.php +++ b/src/Controller/AdminPages/MeasurementUnitController.php @@ -34,7 +34,7 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * @see \App\Tests\Controller\AdminPages\MeasurementUnitControllerTest @@ -55,7 +55,7 @@ class MeasurementUnitController extends BaseAdminController return $this->_delete($request, $entity, $recursionHelper); } - #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'measurement_unit_edit')] + #[Route(path: '/{id}/edit/{timestamp}', name: 'measurement_unit_edit', requirements: ['id' => '\d+'])] #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(MeasurementUnit $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { diff --git a/src/Controller/AdminPages/PartCustomStateController.php b/src/Controller/AdminPages/PartCustomStateController.php new file mode 100644 index 00000000..60f63abf --- /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/AdminPages/ProjectAdminController.php b/src/Controller/AdminPages/ProjectAdminController.php index 16bf6df1..41287b69 100644 --- a/src/Controller/AdminPages/ProjectAdminController.php +++ b/src/Controller/AdminPages/ProjectAdminController.php @@ -33,7 +33,7 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; #[Route(path: '/project')] class ProjectAdminController extends BaseAdminController @@ -51,7 +51,7 @@ class ProjectAdminController extends BaseAdminController return $this->_delete($request, $entity, $recursionHelper); } - #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'project_edit')] + #[Route(path: '/{id}/edit/{timestamp}', name: 'project_edit', requirements: ['id' => '\d+'])] #[Route(path: '/{id}/edit', requirements: ['id' => '\d+'])] public function edit(Project $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { diff --git a/src/Controller/AdminPages/StorelocationController.php b/src/Controller/AdminPages/StorageLocationController.php similarity index 71% rename from src/Controller/AdminPages/StorelocationController.php rename to src/Controller/AdminPages/StorageLocationController.php index 7a9850ab..def6d3c6 100644 --- a/src/Controller/AdminPages/StorelocationController.php +++ b/src/Controller/AdminPages/StorageLocationController.php @@ -22,9 +22,9 @@ declare(strict_types=1); namespace App\Controller\AdminPages; -use App\Entity\Attachments\StorelocationAttachment; -use App\Entity\Parameters\StorelocationParameter; -use App\Entity\Parts\Storelocation; +use App\Entity\Attachments\StorageLocationAttachment; +use App\Entity\Parameters\StorageLocationParameter; +use App\Entity\Parts\StorageLocation; use App\Form\AdminPages\StorelocationAdminForm; use App\Services\ImportExportSystem\EntityExporter; use App\Services\ImportExportSystem\EntityImporter; @@ -33,30 +33,30 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * @see \App\Tests\Controller\AdminPages\StorelocationControllerTest */ #[Route(path: '/store_location')] -class StorelocationController extends BaseAdminController +class StorageLocationController extends BaseAdminController { - protected string $entity_class = Storelocation::class; + protected string $entity_class = StorageLocation::class; protected string $twig_template = 'admin/storelocation_admin.html.twig'; protected string $form_class = StorelocationAdminForm::class; protected string $route_base = 'store_location'; - protected string $attachment_class = StorelocationAttachment::class; - protected ?string $parameter_class = StorelocationParameter::class; + protected string $attachment_class = StorageLocationAttachment::class; + protected ?string $parameter_class = StorageLocationParameter::class; #[Route(path: '/{id}', name: 'store_location_delete', methods: ['DELETE'])] - public function delete(Request $request, Storelocation $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse + public function delete(Request $request, StorageLocation $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { return $this->_delete($request, $entity, $recursionHelper); } - #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'store_location_edit')] + #[Route(path: '/{id}/edit/{timestamp}', name: 'store_location_edit', requirements: ['id' => '\d+'])] #[Route(path: '/{id}', requirements: ['id' => '\d+'])] - public function edit(Storelocation $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response + public function edit(StorageLocation $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { return $this->_edit($entity, $request, $em, $timestamp); } @@ -64,7 +64,7 @@ class StorelocationController extends BaseAdminController #[Route(path: '/new', name: 'store_location_new')] #[Route(path: '/{id}/clone', name: 'store_location_clone')] #[Route(path: '/')] - public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Storelocation $entity = null): Response + public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?StorageLocation $entity = null): Response { return $this->_new($request, $em, $importer, $entity); } @@ -76,7 +76,7 @@ class StorelocationController extends BaseAdminController } #[Route(path: '/{id}/export', name: 'store_location_export')] - public function exportEntity(Storelocation $entity, EntityExporter $exporter, Request $request): Response + public function exportEntity(StorageLocation $entity, EntityExporter $exporter, Request $request): Response { return $this->_exportEntity($entity, $exporter, $request); } diff --git a/src/Controller/AdminPages/SupplierController.php b/src/Controller/AdminPages/SupplierController.php index 4cd5e46b..195b9e18 100644 --- a/src/Controller/AdminPages/SupplierController.php +++ b/src/Controller/AdminPages/SupplierController.php @@ -33,7 +33,7 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * @see \App\Tests\Controller\AdminPages\SupplierControllerTest @@ -54,7 +54,7 @@ class SupplierController extends BaseAdminController return $this->_delete($request, $entity, $recursionHelper); } - #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'supplier_edit')] + #[Route(path: '/{id}/edit/{timestamp}', name: 'supplier_edit', requirements: ['id' => '\d+'])] #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(Supplier $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { diff --git a/src/Controller/AttachmentFileController.php b/src/Controller/AttachmentFileController.php index 16b07ab9..81369e12 100644 --- a/src/Controller/AttachmentFileController.php +++ b/src/Controller/AttachmentFileController.php @@ -24,19 +24,20 @@ namespace App\Controller; 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; use Symfony\Component\HttpFoundation\BinaryFileResponse; -use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\ResponseHeaderBag; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; class AttachmentFileController extends AbstractController { @@ -52,15 +53,15 @@ class AttachmentFileController extends AbstractController $this->denyAccessUnlessGranted('show_private', $attachment); } - if ($attachment->isExternal()) { - throw new RuntimeException('You can not download external attachments!'); + if (!$attachment->hasInternal()) { + throw $this->createNotFoundException('The file for this attachment is external and not stored locally!'); } - if (!$helper->isFileExisting($attachment)) { - throw new RuntimeException('The file associated with the attachment is not existing!'); + 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 @@ -81,15 +82,15 @@ class AttachmentFileController extends AbstractController $this->denyAccessUnlessGranted('show_private', $attachment); } - if ($attachment->isExternal()) { - throw new RuntimeException('You can not download external attachments!'); + if (!$attachment->hasInternal()) { + throw $this->createNotFoundException('The file for this attachment is external and not stored locally!'); } - if (!$helper->isFileExisting($attachment)) { - throw new RuntimeException('The file associated with the attachment is not existing!'); + 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 @@ -99,7 +100,8 @@ class AttachmentFileController extends AbstractController } #[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'); @@ -111,7 +113,7 @@ class AttachmentFileController extends AbstractController $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 00000000..2d3dd7f6 --- /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/ErrorHandling/FixedErrorController.php b/src/Controller/ErrorHandling/FixedErrorController.php new file mode 100644 index 00000000..610a07b1 --- /dev/null +++ b/src/Controller/ErrorHandling/FixedErrorController.php @@ -0,0 +1,64 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Controller\ErrorHandling; + +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ErrorController; + +/** + * This class decorates the default error decorator and changes the content type of responses, if it went through a + * Turbo request. + * The problem is, that the default error controller returns in the format of the preferred content type of the request. + * This is turbo-stream. This causes Turbo to try to integrate it into the content frame and not trigger the ajax failed + * events to show the error in a popup like intended. + */ +#[AsDecorator("error_controller")] +class FixedErrorController +{ + public function __construct(private readonly ErrorController $decorated) + {} + + public function __invoke(\Throwable $exception): Response + { + $response = ($this->decorated)($exception); + + //Check the content type of the response + $contentType = $response->headers->get('Content-Type'); + + //If the content type is turbo stream, change the content type to html + //This prevents Turbo to render the response as a turbo stream, and forces to render it in the popup + if ($contentType === 'text/vnd.turbo-stream.html') { + $response->headers->set('Content-Type', 'text/html'); + } + + return $response; + } + + public function preview(Request $request, int $code): Response + { + return ($this->decorated)->preview($request, $code); + } +} \ No newline at end of file diff --git a/src/Controller/GroupController.php b/src/Controller/GroupController.php index 718f0eba..cf7f0ab8 100644 --- a/src/Controller/GroupController.php +++ b/src/Controller/GroupController.php @@ -37,7 +37,7 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; #[Route(path: '/group')] class GroupController extends BaseAdminController @@ -49,7 +49,7 @@ class GroupController extends BaseAdminController protected string $attachment_class = GroupAttachment::class; protected ?string $parameter_class = GroupParameter::class; - #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'group_edit')] + #[Route(path: '/{id}/edit/{timestamp}', name: 'group_edit', requirements: ['id' => '\d+'])] #[Route(path: '/{id}/', requirements: ['id' => '\d+'])] public function edit(Group $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, PermissionSchemaUpdater $permissionSchemaUpdater, ?string $timestamp = null): Response { diff --git a/src/Controller/HomepageController.php b/src/Controller/HomepageController.php index 914ae8bc..076e790b 100644 --- a/src/Controller/HomepageController.php +++ b/src/Controller/HomepageController.php @@ -25,42 +25,22 @@ namespace App\Controller; use App\DataTables\LogDataTable; use App\Entity\Parts\Part; use App\Services\Misc\GitVersionInfo; +use App\Services\System\BannerHelper; use App\Services\System\UpdateAvailableManager; use Doctrine\ORM\EntityManagerInterface; -use const DIRECTORY_SEPARATOR; use Omines\DataTablesBundle\DataTableFactory; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\KernelInterface; -use Symfony\Component\Routing\Annotation\Route; -use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Component\Routing\Attribute\Route; class HomepageController extends AbstractController { - public function __construct(protected CacheInterface $cache, protected KernelInterface $kernel, protected DataTableFactory $dataTable) + public function __construct(private readonly DataTableFactory $dataTable, private readonly BannerHelper $bannerHelper) { } - public function getBanner(): string - { - $banner = $this->getParameter('partdb.banner'); - if (!is_string($banner)) { - throw new \RuntimeException('The parameter "partdb.banner" must be a string.'); - } - if (empty($banner)) { - $banner_path = $this->kernel->getProjectDir() - .DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'banner.md'; - $tmp = file_get_contents($banner_path); - if (false === $tmp) { - throw new \RuntimeException('The banner file could not be read.'); - } - $banner = $tmp; - } - - return $banner; - } #[Route(path: '/', name: 'homepage')] public function homepage(Request $request, GitVersionInfo $versionInfo, EntityManagerInterface $entityManager, @@ -96,7 +76,7 @@ class HomepageController extends AbstractController } return $this->render('homepage.html.twig', [ - 'banner' => $this->getBanner(), + 'banner' => $this->bannerHelper->getBanner(), 'git_branch' => $versionInfo->getGitBranchName(), 'git_commit' => $versionInfo->getGitCommitHash(), 'show_first_steps' => $show_first_steps, diff --git a/src/Controller/InfoProviderController.php b/src/Controller/InfoProviderController.php index cb95377b..b79c307c 100644 --- a/src/Controller/InfoProviderController.php +++ b/src/Controller/InfoProviderController.php @@ -23,28 +23,39 @@ declare(strict_types=1); namespace App\Controller; -use App\Exceptions\AttachmentDownloadException; +use App\Entity\Parts\Manufacturer; +use App\Entity\Parts\Part; +use App\Exceptions\OAuthReconnectRequiredException; use App\Form\InfoProviderSystem\PartSearchType; -use App\Form\Part\PartBaseType; -use App\Services\Attachments\AttachmentSubmitHandler; +use App\Services\InfoProviderSystem\ExistingPartFinder; use App\Services\InfoProviderSystem\PartInfoRetriever; use App\Services\InfoProviderSystem\ProviderRegistry; -use App\Services\LogSystem\EventCommentHelper; -use App\Services\Parts\PartFormHelper; +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\FormInterface; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\HttpClient\Exception\ClientException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; -use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Component\Routing\Attribute\Route; + +use function Symfony\Component\Translation\t; #[Route('/tools/info_providers')] class InfoProviderController extends AbstractController { public function __construct(private readonly ProviderRegistry $providerRegistry, - private readonly PartInfoRetriever $infoRetriever) + private readonly PartInfoRetriever $infoRetriever, + private readonly ExistingPartFinder $existingPartFinder, + private readonly SettingsManagerInterface $settingsManager, + private readonly SettingsFormFactoryInterface $settingsFormFactory + ) { } @@ -60,8 +71,51 @@ class InfoProviderController extends AbstractController ]); } + #[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')] - public function search(Request $request): Response + #[Route('/update/{target}', name: 'info_providers_update_part_search')] + public function search(Request $request, #[MapEntity(id: 'target')] ?Part $update_target, LoggerInterface $exceptionLogger, InfoProviderGeneralSettings $infoProviderSettings): Response { $this->denyAccessUnlessGranted('@info_providers.create_parts'); @@ -70,16 +124,78 @@ class InfoProviderController extends AbstractController $results = null; + //When we are updating a part, use its name as keyword, to make searching easier + //However we can only do this, if the form was not submitted yet + if ($update_target !== null && !$form->isSubmitted()) { + //Use the provider reference if available, otherwise use the manufacturer product number + $keyword = $update_target->getProviderReference()->getProviderId() ?? $update_target->getManufacturerProductNumber(); + //Or the name if both are not available + if ($keyword === "") { + $keyword = $update_target->getName(); + } + + $form->get('keyword')->setData($keyword); + + //If we are updating a part, which already has a provider, preselect that provider in the form + if ($update_target->getProviderReference()->getProviderKey() !== null) { + try { + $form->get('providers')->setData([$this->providerRegistry->getProviderByKey($update_target->getProviderReference()->getProviderKey())]); + } catch (\InvalidArgumentException $e) { + //If the provider is not found, just ignore it + } + } + } + + //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(); - $results = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers); + $dtos = []; + + try { + $dtos = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers); + } catch (ClientException $e) { + $this->addFlash('error', t('info_providers.search.error.client_exception')); + $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 + $results = array_map(function ($result) {return ['dto' => $result, 'localPart' => null];}, $dtos); + if(!$update_target) { + foreach ($results as $index => $result) { + $results[$index]['localPart'] = $this->existingPartFinder->findFirstExisting($result['dto']); + } + } } return $this->render('info_providers/search/part_search.html.twig', [ 'form' => $form, 'results' => $results, + 'update_target' => $update_target ]); } -} \ No newline at end of file +} diff --git a/src/Controller/KiCadApiController.php b/src/Controller/KiCadApiController.php new file mode 100644 index 00000000..c28e87a6 --- /dev/null +++ b/src/Controller/KiCadApiController.php @@ -0,0 +1,85 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Controller; + +use App\Entity\Parts\Category; +use App\Entity\Parts\Part; +use App\Services\EDA\KiCadHelper; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; + +/** + * @see \App\Tests\Controller\KiCadApiControllerTest + */ +#[Route('/kicad-api/v1')] +class KiCadApiController extends AbstractController +{ + public function __construct( + private readonly KiCadHelper $kiCADHelper, + ) + { + } + + #[Route('/', name: 'kicad_api_root')] + public function root(): Response + { + $this->denyAccessUnlessGranted('HAS_ACCESS_PERMISSIONS'); + + //The API documentation says this can be either blank or the URL to the endpoints + return $this->json([ + 'categories' => '', + 'parts' => '', + ]); + } + + #[Route('/categories.json', name: 'kicad_api_categories')] + public function categories(): Response + { + $this->denyAccessUnlessGranted('@categories.read'); + + return $this->json($this->kiCADHelper->getCategories()); + } + + #[Route('/parts/category/{category}.json', name: 'kicad_api_category')] + public function categoryParts(?Category $category): Response + { + if ($category !== null) { + $this->denyAccessUnlessGranted('read', $category); + } else { + $this->denyAccessUnlessGranted('@categories.read'); + } + $this->denyAccessUnlessGranted('@parts.read'); + + return $this->json($this->kiCADHelper->getCategoryParts($category)); + } + + #[Route('/parts/{part}.json', name: 'kicad_api_part')] + public function partDetails(Part $part): Response + { + $this->denyAccessUnlessGranted('read', $part); + + return $this->json($this->kiCADHelper->getKiCADPart($part)); + } +} \ No newline at end of file diff --git a/src/Controller/LabelController.php b/src/Controller/LabelController.php index f5da187e..90a6715b 100644 --- a/src/Controller/LabelController.php +++ b/src/Controller/LabelController.php @@ -53,18 +53,20 @@ use App\Services\ElementTypeNameGenerator; use App\Services\LabelSystem\LabelGenerator; use App\Services\Misc\RangeParser; use Doctrine\ORM\EntityManagerInterface; -use InvalidArgumentException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormError; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +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 + ) { } @@ -86,6 +88,7 @@ class LabelController extends AbstractController $form = $this->createForm(LabelDialogType::class, null, [ 'disable_options' => $disable_options, + 'profile' => $profile ]); //Try to parse given target_type and target_id @@ -109,8 +112,68 @@ class LabelController extends AbstractController $pdf_data = null; $filename = 'invalid.pdf'; - //Generate PDF either when the form is submitted and valid, or the form was not submit yet, and generate is set if (($form->isSubmitted() && $form->isValid()) || ($generate && !$form->isSubmitted() && $profile instanceof LabelProfile)) { + + //Check if the label should be saved as profile + if ($form->get('save_profile')->isClicked() && $this->isGranted('@labels.create_profiles')) { //@phpstan-ignore-line Phpstan does not recognize the isClicked method + //Retrieve the profile name from the form + $new_name = $form->get('save_profile_name')->getData(); + //ensure that the name is not empty + if ($new_name === '' || $new_name === null) { + $form->get('save_profile_name')->addError(new FormError($this->translator->trans('label_generator.profile_name_empty'))); + goto render; + } + + $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_updated'); + + return $this->redirectToRoute('label_dialog_profile', [ + 'profile' => $profile->getID(), + 'target_id' => (string) $form->get('target_id')->getData() + ]); + } + $target_id = (string) $form->get('target_id')->getData(); $targets = $this->findObjects($form_options->getSupportedElement(), $target_id); if ($targets !== []) { @@ -118,7 +181,7 @@ class LabelController extends AbstractController $pdf_data = $this->labelGenerator->generateLabel($form_options, $targets); $filename = $this->getLabelName($targets[0], $profile); } catch (TwigModeException $exception) { - $form->get('options')->get('lines')->addError(new FormError($exception->getMessage())); + $form->get('options')->get('lines')->addError(new FormError($exception->getSafeMessage())); } } else { //$this->addFlash('warning', 'label_generator.no_entities_found'); @@ -133,6 +196,7 @@ class LabelController extends AbstractController } } + render: return $this->render('label_system/dialog.html.twig', [ 'form' => $form, 'pdf_data' => $pdf_data, @@ -153,7 +217,7 @@ class LabelController extends AbstractController { $id_array = $this->rangeParser->parse($ids); - /** @var DBElementRepository $repo */ + /** @var DBElementRepository $repo */ $repo = $this->em->getRepository($type->getEntityClass()); return $repo->getElementsFromIDArray($id_array); diff --git a/src/Controller/LogController.php b/src/Controller/LogController.php index afa32f7a..8aed44e8 100644 --- a/src/Controller/LogController.php +++ b/src/Controller/LogController.php @@ -22,7 +22,6 @@ declare(strict_types=1); namespace App\Controller; -use App\DataTables\Column\LogEntryTargetColumn; use App\DataTables\Filters\LogFilter; use App\DataTables\LogDataTable; use App\Entity\Base\AbstractDBElement; @@ -39,16 +38,15 @@ use App\Services\LogSystem\LogEntryExtraFormatter; 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 Doctrine\ORM\EntityRepository; use InvalidArgumentException; use Omines\DataTablesBundle\DataTableFactory; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; #[Route(path: '/log')] class LogController extends AbstractController @@ -61,7 +59,7 @@ class LogController extends AbstractController } #[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'); @@ -75,7 +73,7 @@ class LogController extends AbstractController $table = $dataTable->createFromType(LogDataTable::class, [ 'filter' => $filter, - ]) + ], ['pageLength' => $tableSettings->fullDefaultPageSize]) ->handleRequest($request); if ($table->isCallback()) { @@ -154,7 +152,7 @@ class LogController extends AbstractController if (EventUndoMode::UNDO === $mode) { $this->undoLog($log_element); - } elseif (EventUndoMode::REVERT === $mode) { + } else { $this->revertLog($log_element); } diff --git a/src/Controller/OAuthClientController.php b/src/Controller/OAuthClientController.php index ff2aab0e..9606a4e4 100644 --- a/src/Controller/OAuthClientController.php +++ b/src/Controller/OAuthClientController.php @@ -28,7 +28,7 @@ use KnpU\OAuth2ClientBundle\Client\ClientRegistry; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use function Symfony\Component\Translation\t; @@ -51,7 +51,7 @@ class OAuthClientController extends AbstractController } #[Route('/{name}/check', name: 'oauth_client_check')] - public function check(string $name, Request $request): Response + public function check(string $name): Response { $this->denyAccessUnlessGranted('@system.manage_oauth_tokens'); diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php index 0537fd6a..3a121ad2 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -23,12 +23,13 @@ declare(strict_types=1); namespace App\Controller; use App\DataTables\LogDataTable; +use App\Entity\Attachments\AttachmentUpload; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Orderdetail; use App\Entity\ProjectSystem\Project; @@ -36,6 +37,7 @@ use App\Exceptions\AttachmentDownloadException; use App\Form\Part\PartBaseType; use App\Services\Attachments\AttachmentSubmitHandler; use App\Services\Attachments\PartPreviewGenerator; +use App\Services\EntityMergers\Mergers\PartMerger; use App\Services\InfoProviderSystem\PartInfoRetriever; use App\Services\LogSystem\EventCommentHelper; use App\Services\LogSystem\HistoryHelper; @@ -44,32 +46,37 @@ use App\Services\Parameters\ParameterExtractor; use App\Services\Parts\PartLotWithdrawAddHelper; use App\Services\Parts\PricedetailHelper; use App\Services\ProjectSystem\ProjectBuildPartHelper; +use App\Settings\BehaviorSettings\PartInfoSettings; +use App\Settings\MiscSettings\IpnSuggestSettings; use DateTime; use Doctrine\ORM\EntityManagerInterface; use Exception; use Omines\DataTablesBundle\DataTableFactory; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Bridge\Doctrine\Attribute\MapEntity; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Contracts\Translation\TranslatorInterface; 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, + private readonly IpnSuggestSettings $ipnSuggestSettings, + ) { } /** @@ -78,9 +85,16 @@ class PartController extends AbstractController */ #[Route(path: '/{id}/info/{timestamp}', name: 'part_info')] #[Route(path: '/{id}', requirements: ['id' => '\d+'])] - public function show(Part $part, Request $request, TimeTravel $timeTravel, HistoryHelper $historyHelper, - DataTableFactory $dataTable, ParameterExtractor $parameterExtractor, PartLotWithdrawAddHelper $withdrawAddHelper, ?string $timestamp = null): Response - { + public function show( + Part $part, + Request $request, + TimeTravel $timeTravel, + HistoryHelper $historyHelper, + DataTableFactory $dataTable, + ParameterExtractor $parameterExtractor, + PartLotWithdrawAddHelper $withdrawAddHelper, + ?string $timestamp = null + ): Response { $this->denyAccessUnlessGranted('read', $part); $timeTravel_timestamp = null; @@ -118,8 +132,8 @@ class PartController extends AbstractController '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, ] ); @@ -130,7 +144,43 @@ class PartController extends AbstractController { $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'])] @@ -138,7 +188,7 @@ class PartController extends AbstractController { $this->denyAccessUnlessGranted('delete', $part); - if ($this->isCsrfTokenValid('delete'.$part->getID(), $request->request->get('_token'))) { + if ($this->isCsrfTokenValid('delete' . $part->getID(), $request->request->get('_token'))) { $this->commentHelper->setMessage($request->request->get('log_comment', null)); @@ -157,11 +207,15 @@ class PartController extends AbstractController #[Route(path: '/new', name: 'part_new')] #[Route(path: '/{id}/clone', name: 'part_clone')] #[Route(path: '/new_build_part/{project_id}', name: 'part_new_build_part')] - public function new(Request $request, EntityManagerInterface $em, TranslatorInterface $translator, - AttachmentSubmitHandler $attachmentSubmitHandler, ProjectBuildPartHelper $projectBuildPartHelper, + public function new( + Request $request, + EntityManagerInterface $em, + TranslatorInterface $translator, + AttachmentSubmitHandler $attachmentSubmitHandler, + ProjectBuildPartHelper $projectBuildPartHelper, #[MapEntity(mapping: ['id' => 'id'])] ?Part $part = null, - #[MapEntity(mapping: ['project_id' => 'id'])] ?Project $project = null): Response - { + #[MapEntity(mapping: ['project_id' => 'id'])] ?Project $project = null + ): Response { if ($part instanceof Part) { //Clone part @@ -201,8 +255,8 @@ class PartController extends AbstractController } $store_id = $request->get('storelocation', null); - $storelocation = $store_id ? $em->find(Storelocation::class, $store_id) : null; - if ($storelocation instanceof Storelocation && $new_part->getPartLots()->isEmpty()) { + $storelocation = $store_id ? $em->find(StorageLocation::class, $store_id) : null; + if ($storelocation instanceof StorageLocation && $new_part->getPartLots()->isEmpty()) { $partLot = new PartLot(); $partLot->setStorageLocation($storelocation); $partLot->setInstockUnknown(true); @@ -228,11 +282,74 @@ class PartController extends AbstractController $dto = $infoRetriever->getDetails($providerKey, $providerId); $new_part = $infoRetriever->dtoToPart($dto); + if ($new_part->getCategory() === null || $new_part->getCategory()->getID() === null) { + $this->addFlash('warning', t("part.create_from_info_provider.no_category_yet")); + } + return $this->renderPartForm('new', $request, $new_part, [ 'info_provider_dto' => $dto, ]); } + #[Route('/{target}/merge/{other}', name: 'part_merge')] + public function merge(Request $request, Part $target, Part $other, PartMerger $partMerger): Response + { + $this->denyAccessUnlessGranted('edit', $target); + $this->denyAccessUnlessGranted('delete', $other); + + //Save the old name of the target part for the template + $target_name = $target->getName(); + + $this->addFlash('notice', t('part.merge.flash.please_review')); + + $merged = $partMerger->merge($target, $other); + return $this->renderPartForm('merge', $request, $merged, [], [ + 'tname_before' => $target_name, + 'other_part' => $other, + ]); + } + + #[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 { + $this->denyAccessUnlessGranted('edit', $part); + $this->denyAccessUnlessGranted('@info_providers.create_parts'); + + //Save the old name of the target part for the template + $old_name = $part->getName(); + + $dto = $infoRetriever->getDetails($providerKey, $providerId); + $provider_part = $infoRetriever->dtoToPart($dto); + + $part = $partMerger->merge($part, $provider_part); + + $this->addFlash('notice', t('part.merge.flash.please_review')); + + // Check if this is part of a bulk import job + $jobId = $request->query->get('jobId'); + $bulkJob = null; + if ($jobId) { + $bulkJob = $this->em->getRepository(\App\Entity\InfoProviderSystem\BulkInfoProviderImportJob::class)->find($jobId); + // Verify user owns this job + if ($bulkJob && $bulkJob->getCreatedBy() !== $this->getUser()) { + $bulkJob = null; + } + } + + return $this->renderPartForm('update_from_ip', $request, $part, [ + 'info_provider_dto' => $dto, + ], [ + 'tname_before' => $old_name, + 'bulk_job' => $bulkJob + ]); + } + /** * This function provides a common implementation for methods, which use the part form. * @param Request $request @@ -240,10 +357,10 @@ class PartController extends AbstractController * @param array $form_options * @return Response */ - private function renderPartForm(string $mode, Request $request, Part $data, array $form_options = []): Response + private function renderPartForm(string $mode, Request $request, Part $data, array $form_options = [], array $merge_infos = []): Response { //Ensure that mode is either 'new' or 'edit - if (!in_array($mode, ['new', 'edit'], true)) { + if (!in_array($mode, ['new', 'edit', 'merge', 'update_from_ip'], true)) { throw new \InvalidArgumentException('Invalid mode given'); } @@ -258,28 +375,35 @@ class PartController extends AbstractController $attachments = $form['attachments']; foreach ($attachments as $attachment) { /** @var FormInterface $attachment */ - $options = [ - 'secure_attachment' => $attachment['secureFile']->getData(), - 'download_url' => $attachment['downloadURL']->getData(), - ]; try { - $this->attachmentSubmitHandler->handleFormSubmit($attachment->getData(), $attachment['file']->getData(), $options); + $this->attachmentSubmitHandler->handleUpload($attachment->getData(), AttachmentUpload::fromAttachmentForm($attachment)); } catch (AttachmentDownloadException $attachmentDownloadException) { $this->addFlash( 'error', - $this->translator->trans('attachment.download_failed').' '.$attachmentDownloadException->getMessage() + $this->translator->trans('attachment.download_failed') . ' ' . $attachmentDownloadException->getMessage() ); } } + //Ensure that the master picture is still part of the attachments + if ($new_part->getMasterPictureAttachment() !== null && !$new_part->getAttachments()->contains($new_part->getMasterPictureAttachment())) { + $new_part->setMasterPictureAttachment(null); + } + $this->commentHelper->setMessage($form['log_comment']->getData()); $this->em->persist($new_part); + + //When we are in merge mode, we have to remove the other part + if ($mode === 'merge') { + $this->em->remove($merge_infos['other_part']); + } + $this->em->flush(); if ($mode === 'new') { $this->addFlash('success', 'part.created_flash'); - } else if ($mode === 'edit') { + } elseif ($mode === 'edit') { $this->addFlash('success', 'part.edited_flash'); } @@ -298,6 +422,12 @@ class PartController extends AbstractController return $this->redirectToRoute('part_new'); } + // Check if we're in bulk import mode and preserve jobId + $jobId = $request->query->get('jobId'); + if ($jobId && isset($merge_infos['bulk_job'])) { + return $this->redirectToRoute('part_edit', ['id' => $new_part->getID(), 'jobId' => $jobId]); + } + return $this->redirectToRoute('part_edit', ['id' => $new_part->getID()]); } @@ -308,35 +438,47 @@ class PartController extends AbstractController $template = ''; if ($mode === 'new') { $template = 'parts/edit/new_part.html.twig'; - } else if ($mode === 'edit') { + } elseif ($mode === 'edit') { $template = 'parts/edit/edit_part_info.html.twig'; + } elseif ($mode === 'merge') { + $template = 'parts/edit/merge_parts.html.twig'; + } elseif ($mode === 'update_from_ip') { + $template = 'parts/edit/update_from_ip.html.twig'; } - return $this->render($template, + $partRepository = $this->em->getRepository(Part::class); + + return $this->render( + $template, [ 'part' => $new_part, + 'ipnSuggestions' => $partRepository->autoCompleteIpn($data, $data->getDescription(), $this->ipnSuggestSettings->suggestPartDigits), 'form' => $form, - ]); + 'merge_old_name' => $merge_infos['tname_before'] ?? null, + 'merge_other' => $merge_infos['other_part'] ?? null, + 'bulk_job' => $merge_infos['bulk_job'] ?? null, + 'jobId' => $request->query->get('jobId') + ] + ); } - #[Route(path: '/{id}/add_withdraw', name: 'part_add_withdraw', methods: ['POST'])] public function withdrawAddHandler(Part $part, Request $request, EntityManagerInterface $em, PartLotWithdrawAddHelper $withdrawAddHelper): Response { if ($this->isCsrfTokenValid('part_withraw' . $part->getID(), $request->request->get('_csfr'))) { //Retrieve partlot from the request $partLot = $em->find(PartLot::class, $request->request->get('lot_id')); - if(!$partLot instanceof PartLot) { + if (!$partLot instanceof PartLot) { throw new \RuntimeException('Part lot not found!'); } //Ensure that the partlot belongs to the part - if($partLot->getPart() !== $part) { + if ($partLot->getPart() !== $part) { throw new \RuntimeException("The origin partlot does not belong to the part!"); } //Try to determine the target lot (used for move actions), if the parameter is existing $targetId = $request->request->get('target_id', null); - $targetLot = $targetId ? $em->find(PartLot::class, $targetId) : null; + $targetLot = $targetId ? $em->find(PartLot::class, $targetId) : null; if ($targetLot && $targetLot->getPart() !== $part) { throw new \RuntimeException("The target partlot does not belong to the part!"); } @@ -345,23 +487,41 @@ class PartController extends AbstractController $amount = (float) $request->request->get('amount'); $comment = $request->request->get('comment'); $action = $request->request->get('action'); + $delete_lot_if_empty = $request->request->getBoolean('delete_lot_if_empty', false); + $timestamp = null; + $timestamp_str = $request->request->getString('timestamp', ''); + //Try to parse the timestamp + if ($timestamp_str !== '') { + $timestamp = new DateTime($timestamp_str); + } + + //Ensure that the timestamp is not in the future + if ($timestamp !== null && $timestamp > new DateTime("+20min")) { + throw new \LogicException("The timestamp must not be in the future!"); + } + + //Ensure that the amount is not null or negative + if ($amount <= 0) { + $this->addFlash('warning', 'part.withdraw.zero_amount'); + goto err; + } try { switch ($action) { case "withdraw": case "remove": $this->denyAccessUnlessGranted('withdraw', $partLot); - $withdrawAddHelper->withdraw($partLot, $amount, $comment); + $withdrawAddHelper->withdraw($partLot, $amount, $comment, $timestamp, $delete_lot_if_empty); break; case "add": $this->denyAccessUnlessGranted('add', $partLot); - $withdrawAddHelper->add($partLot, $amount, $comment); + $withdrawAddHelper->add($partLot, $amount, $comment, $timestamp); break; case "move": $this->denyAccessUnlessGranted('move', $partLot); $this->denyAccessUnlessGranted('move', $targetLot); - $withdrawAddHelper->move($partLot, $targetLot, $amount, $comment); + $withdrawAddHelper->move($partLot, $targetLot, $amount, $comment, $timestamp, $delete_lot_if_empty); break; default: throw new \RuntimeException("Unknown action!"); @@ -381,7 +541,7 @@ class PartController extends AbstractController err: //If a redirect was passed, then redirect there - if($request->request->get('_redirect')) { + if ($request->request->get('_redirect')) { return $this->redirect($request->request->get('_redirect')); } //Otherwise just redirect to the part page diff --git a/src/Controller/PartImportExportController.php b/src/Controller/PartImportExportController.php index 326ea923..45f90d75 100644 --- a/src/Controller/PartImportExportController.php +++ b/src/Controller/PartImportExportController.php @@ -32,7 +32,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use UnexpectedValueException; @@ -112,8 +112,9 @@ class PartImportExportController extends AbstractController $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 90005447..808b0c5d 100644 --- a/src/Controller/PartListsController.php +++ b/src/Controller/PartListsController.php @@ -29,29 +29,49 @@ use App\DataTables\PartsDataTable; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\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; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormInterface; -use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +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'])] public function tableAction(Request $request, PartsTableActionHandler $actionHandler): Response { @@ -61,6 +81,7 @@ class PartListsController extends AbstractController $ids = $request->request->get('ids'); $action = $request->request->get('action'); $target = $request->request->get('target'); + $redirectResponse = null; if (!$this->isCsrfTokenValid('table_action', $request->request->get('_token'))) { $this->addFlash('error', 'csfr_invalid'); @@ -71,17 +92,36 @@ class PartListsController extends AbstractController 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. - if (isset($redirectResponse) && $redirectResponse instanceof Response) { + if ($redirectResponse !== null) { return $redirectResponse; } @@ -125,14 +165,21 @@ class PartListsController extends AbstractController $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 @@ class PartListsController extends AbstractController return $this->render($template, array_merge([ 'datatable' => $table, - 'filterForm' => $filterForm->createView(), + 'filterForm' => $filterForm?->createView(), ], $additonal_template_vars)); } @@ -167,7 +214,7 @@ class PartListsController extends AbstractController 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 @@ class PartListsController extends AbstractController 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 @@ class PartListsController extends AbstractController 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')); }, [ @@ -214,19 +261,19 @@ class PartListsController extends AbstractController } #[Route(path: '/store_location/{id}/parts', name: 'part_list_store_location')] - public function showStorelocation(Storelocation $storelocation, Request $request): Response + public function showStorelocation(StorageLocation $storelocation, Request $request): Response { $this->denyAccessUnlessGranted('@storelocations.read'); 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')); }, [ 'entity' => $storelocation, - 'repo' => $this->entityManager->getRepository(Storelocation::class), + 'repo' => $this->entityManager->getRepository(StorageLocation::class), ] ); } @@ -239,7 +286,7 @@ class PartListsController extends AbstractController 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 8c192319..2a6d19ee 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -27,29 +27,24 @@ use App\Entity\Parts\Part; use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; use App\Form\ProjectSystem\ProjectAddPartsType; -use App\Form\ProjectSystem\ProjectBOMEntryCollectionType; use App\Form\ProjectSystem\ProjectBuildType; -use App\Form\Type\StructuralEntityType; use App\Helpers\Projects\ProjectBuildRequest; use App\Services\ImportExportSystem\BOMImporter; use App\Services\ProjectSystem\ProjectBuildHelper; -use App\Validator\Constraints\UniqueObjectCollection; +use App\Settings\BehaviorSettings\TableSettings; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use League\Csv\SyntaxError; use Omines\DataTablesBundle\DataTableFactory; -use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator; +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; use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; -use Symfony\Component\Form\FormEvents; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; -use Symfony\Component\Validator\Constraints\NotNull; +use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Validator\Validator\ValidatorInterface; use function Symfony\Component\Translation\t; @@ -62,11 +57,12 @@ class ProjectController extends AbstractController } #[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()) { @@ -107,9 +103,14 @@ class ProjectController extends AbstractController $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'); @@ -125,9 +126,13 @@ class ProjectController extends AbstractController } #[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(); @@ -143,6 +148,8 @@ class ProjectController extends AbstractController '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, [ @@ -166,25 +173,40 @@ class ProjectController extends AbstractController $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()])); } } @@ -196,11 +218,267 @@ class ProjectController extends AbstractController ]); } + #[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'); @@ -214,6 +492,11 @@ class ProjectController extends AbstractController //Preset the BOM entries with the selected parts, when the form was not submitted yet $preset_data = new ArrayCollection(); foreach (explode(',', (string) $request->get('parts', '')) as $part_id) { + //Skip empty part IDs. Postgres seems to be especially sensitive to empty strings, as it does not allow them in integer columns + if ($part_id === '') { + continue; + } + $part = $entityManager->getRepository(Part::class)->find($part_id); if (null !== $part) { //If there is already a BOM entry for this part, we use this one (we edit it then) @@ -221,7 +504,7 @@ class ProjectController extends AbstractController 'project' => $project, 'part' => $part ]); - if ($bom_entry) { + if ($bom_entry !== null) { $preset_data->add($bom_entry); } else { //Otherwise create an empty one $entry = new ProjectBOMEntry(); @@ -242,7 +525,7 @@ class ProjectController extends AbstractController $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 b0af0119..a4cac3aa 100644 --- a/src/Controller/RedirectController.php +++ b/src/Controller/RedirectController.php @@ -23,6 +23,7 @@ declare(strict_types=1); 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 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ 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 @@ class RedirectController extends AbstractController 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(); @@ -58,7 +59,7 @@ class RedirectController extends AbstractController //If either mod_rewrite is not enabled or the index.php version is enforced, add index.php to the string if (($this->enforce_index_php || !$this->checkIfModRewriteAvailable()) - && !str_contains((string) $new_url, 'index.php')) { + && !str_contains($new_url, 'index.php')) { //Like Request::getUriForPath only with index.php $new_url = $request->getSchemeAndHttpHost().$request->getBaseUrl().'/index.php/'.$locale.$request->getPathInfo(); } @@ -73,6 +74,7 @@ class RedirectController extends AbstractController * Check if mod_rewrite is available (URL rewriting is possible). * If this is true, we can redirect to /en, otherwise we have to redirect to index.php/en. * When the PHP is not used via Apache SAPI, we just assume that URL rewriting is available. + * @noinspection PhpUndefinedFunctionInspection */ public function checkIfModRewriteAvailable(): bool { diff --git a/src/Controller/ScanController.php b/src/Controller/ScanController.php index b261f3fd..aebadd89 100644 --- a/src/Controller/ScanController.php +++ b/src/Controller/ScanController.php @@ -42,20 +42,25 @@ declare(strict_types=1); namespace App\Controller; use App\Form\LabelSystem\ScanDialogType; -use App\Services\LabelSystem\Barcodes\BarcodeNormalizer; -use App\Services\LabelSystem\Barcodes\BarcodeRedirector; +use App\Services\LabelSystem\BarcodeScanner\BarcodeRedirector; +use App\Services\LabelSystem\BarcodeScanner\BarcodeScanHelper; +use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType; +use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult; use Doctrine\ORM\EntityNotFoundException; use InvalidArgumentException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; +/** + * @see \App\Tests\Controller\ScanControllerTest + */ #[Route(path: '/scan')] class ScanController extends AbstractController { - public function __construct(protected BarcodeRedirector $barcodeParser, protected BarcodeNormalizer $barcodeNormalizer) + public function __construct(protected BarcodeRedirector $barcodeParser, protected BarcodeScanHelper $barcodeNormalizer) { } @@ -69,16 +74,24 @@ class ScanController extends AbstractController if ($input === null && $form->isSubmitted() && $form->isValid()) { $input = $form['input']->getData(); + $mode = $form['mode']->getData(); } + $infoModeData = null; + if ($input !== null) { try { - [$type, $id] = $this->barcodeNormalizer->normalizeBarcodeContent($input); + $scan_result = $this->barcodeNormalizer->scanBarcodeContent($input, $mode ?? null); + //Perform a redirect if the info mode is not enabled + if (!$form['info_mode']->getData()) { + try { + return $this->redirect($this->barcodeParser->getRedirectURL($scan_result)); + } catch (EntityNotFoundException) { + $this->addFlash('success', 'scan.qr_not_found'); + } + } else { //Otherwise retrieve infoModeData + $infoModeData = $scan_result->getDecodedForInfoMode(); - try { - return $this->redirect($this->barcodeParser->getRedirectURL($type, $id)); - } catch (EntityNotFoundException) { - $this->addFlash('success', 'scan.qr_not_found'); } } catch (InvalidArgumentException) { $this->addFlash('error', 'scan.format_unknown'); @@ -87,6 +100,7 @@ class ScanController extends AbstractController return $this->render('label_system/scanner/scanner.html.twig', [ 'form' => $form, + 'infoModeData' => $infoModeData, ]); } @@ -95,10 +109,23 @@ class ScanController extends AbstractController */ public function scanQRCode(string $type, int $id): Response { + $type = strtolower($type); + try { $this->addFlash('success', 'scan.qr_success'); - return $this->redirect($this->barcodeParser->getRedirectURL($type, $id)); + if (!isset(BarcodeScanHelper::QR_TYPE_MAP[$type])) { + throw new InvalidArgumentException('Unknown type: '.$type); + } + //Construct the scan result manually, as we don't have a barcode here + $scan_result = new LocalBarcodeScanResult( + target_type: BarcodeScanHelper::QR_TYPE_MAP[$type], + target_id: $id, + //The routes are only used on the internal generated QR codes + source_type: BarcodeSourceType::INTERNAL + ); + + return $this->redirect($this->barcodeParser->getRedirectURL($scan_result)); } catch (EntityNotFoundException) { $this->addFlash('success', 'scan.qr_not_found'); diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 9f602a80..4e2077c4 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -39,7 +39,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; diff --git a/src/Controller/SelectAPIController.php b/src/Controller/SelectAPIController.php index c403ab82..c1e682c8 100644 --- a/src/Controller/SelectAPIController.php +++ b/src/Controller/SelectAPIController.php @@ -29,13 +29,14 @@ use App\Entity\Parts\Category; 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; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use Symfony\Contracts\Translation\TranslatorInterface; /** @@ -78,6 +79,12 @@ class SelectAPIController extends AbstractController 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 00000000..15c945f6 --- /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_tools', 'tree_treeview', 'sidebar_tree_update', 'synonyms']); + + $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/StatisticsController.php b/src/Controller/StatisticsController.php index 6ff09e83..67c29781 100644 --- a/src/Controller/StatisticsController.php +++ b/src/Controller/StatisticsController.php @@ -44,7 +44,7 @@ namespace App\Controller; use App\Services\Tools\StatisticsHelper; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; class StatisticsController extends AbstractController { diff --git a/src/Controller/ToolsController.php b/src/Controller/ToolsController.php index e0626e7b..d78aff62 100644 --- a/src/Controller/ToolsController.php +++ b/src/Controller/ToolsController.php @@ -22,18 +22,18 @@ declare(strict_types=1); */ namespace App\Controller; -use App\Services\Attachments\AttachmentPathResolver; use App\Services\Attachments\AttachmentSubmitHandler; use App\Services\Attachments\AttachmentURLGenerator; use App\Services\Attachments\BuiltinAttachmentsFinder; +use App\Services\Doctrine\DBInfoHelper; +use App\Services\Doctrine\NatsortDebugHelper; use App\Services\Misc\GitVersionInfo; -use App\Services\Misc\DBInfoHelper; use App\Services\System\UpdateAvailableManager; -use Doctrine\ORM\EntityManagerInterface; +use App\Settings\AppSettings; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; -use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Runtime\SymfonyRuntime; #[Route(path: '/tools')] class ToolsController extends AbstractController @@ -47,8 +47,9 @@ class ToolsController extends AbstractController } #[Route(path: '/server_infos', name: 'tools_server_infos')] - public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper, - AttachmentSubmitHandler $attachmentSubmitHandler, UpdateAvailableManager $updateAvailableManager): Response + public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper, NatsortDebugHelper $natsortDebugHelper, + AttachmentSubmitHandler $attachmentSubmitHandler, UpdateAvailableManager $updateAvailableManager, + AppSettings $settings): Response { $this->denyAccessUnlessGranted('@system.server_infos'); @@ -56,23 +57,23 @@ class ToolsController extends AbstractController //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'), - 'gpdr_compliance' => $this->getParameter('partdb.gdpr_compliance'), - 'use_gravatar' => $this->getParameter('partdb.users.use_gravatar'), + 'use_gravatar' => $settings->system->privacy->useGravatar, + 'gdpr_compliance' => $this->getParameter('partdb.gdpr_compliance'), 'email_password_reset' => $this->getParameter('partdb.users.email_pw_reset'), - 'enviroment' => $this->getParameter('kernel.environment'), + 'environment' => $this->getParameter('kernel.environment'), 'is_debug' => $this->getParameter('kernel.debug'), 'email_sender' => $this->getParameter('partdb.mail.sender_email'), 'email_sender_name' => $this->getParameter('partdb.mail.sender_name'), - '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'), @@ -80,10 +81,14 @@ class ToolsController extends AbstractController 'php_version' => PHP_VERSION, 'php_uname' => php_uname('a'), 'php_sapi' => PHP_SAPI, + 'php_bit_size' => PHP_INT_SIZE * 8, 'php_extensions' => [...get_loaded_extensions()], 'php_opcache_enabled' => ini_get('opcache.enable'), 'php_upload_max_filesize' => ini_get('upload_max_filesize'), 'php_post_max_size' => ini_get('post_max_size'), + 'kernel_runtime_environment' => $this->getParameter('kernel.runtime_environment'), + 'kernel_runtime_mode' => $this->getParameter('kernel.runtime_mode'), + 'kernel_runtime' => $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? SymfonyRuntime::class, //DB section 'db_type' => $DBInfoHelper->getDatabaseType() ?? 'Unknown', @@ -91,6 +96,8 @@ class ToolsController extends AbstractController 'db_size' => $DBInfoHelper->getDatabaseSize(), 'db_name' => $DBInfoHelper->getDatabaseName() ?? 'Unknown', 'db_user' => $DBInfoHelper->getDatabaseUsername() ?? 'Unknown', + 'db_natsort_method' => $natsortDebugHelper->getNaturalSortMethod(), + 'db_natsort_slow_allowed' => $natsortDebugHelper->isSlowNaturalSortAllowed(), //New version section 'new_version_available' => $updateAvailableManager->isUpdateAvailable(), @@ -105,7 +112,7 @@ class ToolsController extends AbstractController $this->denyAccessUnlessGranted('@tools.builtin_footprints_viewer'); $grouped_footprints = $builtinAttachmentsFinder->getListOfFootprintsGroupedByFolder(); - $grouped_footprints = array_map(fn($group) => array_map(fn($placeholder_filepath) => [ + $grouped_footprints = array_map(static fn($group) => array_map(static fn($placeholder_filepath) => [ 'filename' => basename((string) $placeholder_filepath), 'assets_path' => $urlGenerator->placeholderPathToAssetPath($placeholder_filepath), ], $group), $grouped_footprints); diff --git a/src/Controller/TreeController.php b/src/Controller/TreeController.php index e7ce0b72..71f8ba5c 100644 --- a/src/Controller/TreeController.php +++ b/src/Controller/TreeController.php @@ -27,13 +27,13 @@ use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Services\Trees\ToolsTreeBuilder; use App\Services\Trees\TreeViewGenerator; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; /** * This controller has the purpose to provide the data for all treeviews. @@ -80,10 +80,10 @@ class TreeController extends AbstractController #[Route(path: '/location/{id}', name: 'tree_location')] #[Route(path: '/locations', name: 'tree_location_root')] - public function locationTree(?Storelocation $location = null): JsonResponse + public function locationTree(?StorageLocation $location = null): JsonResponse { if ($this->isGranted('@parts.read') && $this->isGranted('@storelocations.read')) { - $tree = $this->treeGenerator->getTreeView(Storelocation::class, $location, 'list_parts_root'); + $tree = $this->treeGenerator->getTreeView(StorageLocation::class, $location, 'list_parts_root'); } else { return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN); } diff --git a/src/Controller/TypeaheadController.php b/src/Controller/TypeaheadController.php index 3c0d76e9..39821f59 100644 --- a/src/Controller/TypeaheadController.php +++ b/src/Controller/TypeaheadController.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Controller; +use App\Entity\Parameters\AbstractParameter; +use App\Settings\MiscSettings\IpnSuggestSettings; use Symfony\Component\HttpFoundation\Response; use App\Entity\Attachments\Attachment; use App\Entity\Parts\Category; @@ -34,7 +36,7 @@ use App\Entity\Parameters\GroupParameter; use App\Entity\Parameters\ManufacturerParameter; use App\Entity\Parameters\MeasurementUnitParameter; use App\Entity\Parameters\PartParameter; -use App\Entity\Parameters\StorelocationParameter; +use App\Entity\Parameters\StorageLocationParameter; use App\Entity\Parameters\SupplierParameter; use App\Entity\Parts\Part; use App\Entity\PriceInformations\Currency; @@ -48,7 +50,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Asset\Packages; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; @@ -59,8 +61,11 @@ use Symfony\Component\Serializer\Serializer; #[Route(path: '/typeahead')] class TypeaheadController extends AbstractController { - public function __construct(protected AttachmentURLGenerator $urlGenerator, protected Packages $assets) - { + public function __construct( + protected AttachmentURLGenerator $urlGenerator, + protected Packages $assets, + protected IpnSuggestSettings $ipnSuggestSettings, + ) { } #[Route(path: '/builtInResources/search', name: 'typeahead_builtInRessources')] @@ -92,7 +97,7 @@ class TypeaheadController extends AbstractController /** * This function map the parameter type to the class, so we can access its repository - * @return class-string + * @return class-string */ private function typeToParameterClass(string $type): string { @@ -102,7 +107,7 @@ class TypeaheadController extends AbstractController 'device' => ProjectParameter::class, 'footprint' => FootprintParameter::class, 'manufacturer' => ManufacturerParameter::class, - 'storelocation' => StorelocationParameter::class, + 'storelocation' => StorageLocationParameter::class, 'supplier' => SupplierParameter::class, 'attachment_type' => AttachmentTypeParameter::class, 'group' => GroupParameter::class, @@ -155,7 +160,7 @@ class TypeaheadController extends AbstractController //Ensure user has the correct permissions $this->denyAccessUnlessGranted('read', $test_obj); - /** @var ParameterRepository $repository */ + /** @var ParameterRepository $repository */ $repository = $entityManager->getRepository($class); $data = $repository->autocompleteParamName($query); @@ -182,4 +187,30 @@ class TypeaheadController extends AbstractController return new JsonResponse($data, Response::HTTP_OK, [], true); } + + #[Route(path: '/parts/ipn-suggestions', name: 'ipn_suggestions', methods: ['GET'])] + public function ipnSuggestions( + Request $request, + EntityManagerInterface $entityManager + ): JsonResponse { + $partId = $request->query->get('partId'); + if ($partId === '0' || $partId === 'undefined' || $partId === 'null') { + $partId = null; + } + $categoryId = $request->query->getInt('categoryId'); + $description = base64_decode($request->query->getString('description'), true); + + /** @var Part $part */ + $part = $partId !== null ? $entityManager->getRepository(Part::class)->find($partId) : new Part(); + /** @var Category|null $category */ + $category = $entityManager->getRepository(Category::class)->find($categoryId); + + $clonedPart = clone $part; + $clonedPart->setCategory($category); + + $partRepository = $entityManager->getRepository(Part::class); + $ipnSuggestions = $partRepository->autoCompleteIpn($clonedPart, $description, $this->ipnSuggestSettings->suggestPartDigits); + + return new JsonResponse($ipnSuggestions); + } } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index d5190b97..968bd1e3 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -45,7 +45,7 @@ use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Validator\Validator\ValidatorInterface; #[Route(path: '/user')] @@ -78,7 +78,7 @@ class UserController extends BaseAdminController * * @throws Exception */ - #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'user_edit')] + #[Route(path: '/{id}/edit/{timestamp}', name: 'user_edit', requirements: ['id' => '\d+'])] #[Route(path: '/{id}/', requirements: ['id' => '\d+'])] public function edit(User $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, PermissionSchemaUpdater $permissionSchemaUpdater, ValidatorInterface $validator, ?string $timestamp = null): Response @@ -166,11 +166,17 @@ class UserController extends BaseAdminController return $this->_new($request, $em, $importer, $entity); } - #[Route(path: '/{id}', name: 'user_delete', methods: ['DELETE'], requirements: ['id' => '\d+'])] + #[Route(path: '/{id}', name: 'user_delete', requirements: ['id' => '\d+'], methods: ['DELETE'])] public function delete(Request $request, User $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { + //Disallow deleting the anonymous user if (User::ID_ANONYMOUS === $entity->getID()) { - throw new InvalidArgumentException('You can not delete the anonymous user! It is needed for permission checking without a logged in user'); + throw new \LogicException('You can not delete the anonymous user! It is needed for permission checking without a logged in user'); + } + + //Disallow deleting the current logged-in user + if ($entity === $this->getUser()) { + throw new \LogicException('You can not delete your own user account!'); } return $this->_delete($request, $entity, $recursionHelper); diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php index 704bacb7..4e56015a 100644 --- a/src/Controller/UserSettingsController.php +++ b/src/Controller/UserSettingsController.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace App\Controller; use App\Entity\Attachments\Attachment; +use App\Entity\UserSystem\ApiToken; +use App\Entity\UserSystem\ApiTokenLevel; use App\Entity\UserSystem\U2FKey; use App\Entity\UserSystem\User; use App\Entity\UserSystem\WebauthnKey; @@ -34,11 +36,11 @@ use App\Services\UserSystem\TFA\BackupCodeManager; use App\Services\UserSystem\UserAvatarHelper; use Doctrine\ORM\EntityManagerInterface; use RuntimeException; -use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Form\Extension\Core\Type\DateTimeType; +use Symfony\Component\Form\Extension\Core\Type\EnumType; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\RepeatedType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -49,21 +51,15 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Core\Validator\Constraints\UserPassword; use Symfony\Component\Validator\Constraints\Length; #[Route(path: '/user')] class UserSettingsController extends AbstractController { - /** - * @var EventDispatcher|EventDispatcherInterface - */ - protected $eventDispatcher; - - public function __construct(protected bool $demo_mode, EventDispatcherInterface $eventDispatcher) + public function __construct(protected bool $demo_mode, protected EventDispatcherInterface $eventDispatcher) { - $this->eventDispatcher = $eventDispatcher; } #[Route(path: '/2fa_backup_codes', name: 'show_backup_codes')] @@ -244,7 +240,10 @@ class UserSettingsController extends AbstractController $page_need_reload = true; } - /** @var Form $form We need a form implementation for the next calls */ + if (!$form instanceof Form) { + throw new RuntimeException('Form is not an instance of Form, so we cannot retrieve the clicked button!'); + } + //Remove the avatar attachment from the user if requested if ($form->getClickedButton() && 'remove_avatar' === $form->getClickedButton()->getName() && $user->getMasterPictureAttachment() instanceof Attachment) { $em->remove($user->getMasterPictureAttachment()); @@ -395,4 +394,99 @@ class UserSettingsController extends AbstractController ], ]); } + + /** + * @return Response + */ + #[Route('/api_token/create', name: 'user_api_token_create')] + public function addApiToken(Request $request, EntityManagerInterface $entityManager): Response + { + $this->denyAccessUnlessGranted('@api.manage_tokens'); + //When user change its settings, he should be logged in fully. + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); + + $token = new ApiToken(); + if (!$this->getUser() instanceof User) { + throw new RuntimeException('This controller only works only for Part-DB User objects!'); + } + $token->setUser($this->getUser()); + + $secret = null; + + $form = $this->createFormBuilder($token) + ->add('name', TextType::class, [ + 'label' => 'api_tokens.name', + ]) + ->add('level', EnumType::class, [ + 'class' => ApiTokenLevel::class, + 'label' => 'api_tokens.access_level', + 'help' => 'api_tokens.access_level.help', + 'choice_label' => fn (ApiTokenLevel $level) => $level->getTranslationKey(), + ]) + ->add('valid_until', DateTimeType::class, [ + 'label' => 'api_tokens.expiration_date', + 'widget' => 'single_text', + 'help' => 'api_tokens.expiration_date.help', + 'required' => false, + 'html5' => true + ]) + ->add('submit', SubmitType::class, [ + 'label' => 'save', + ]) + ->getForm(); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->persist($token); + $entityManager->flush(); + + $secret = $token->getToken(); + } + + return $this->render('users/api_token_create.html.twig', [ + 'token' => $token, + 'form' => $form, + 'secret' => $secret, + ]); + } + + #[Route(path: '/api_token/delete', name: 'user_api_tokens_delete', methods: ['DELETE'])] + public function apiTokenRemove(Request $request, EntityManagerInterface $entityManager): Response + { + $this->denyAccessUnlessGranted('@api.manage_tokens'); + //When user change its settings, he should be logged in fully. + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); + + $user = $this->getUser(); + if (!$user instanceof User) { + throw new RuntimeException('This controller only works only for Part-DB User objects!'); + } + + if (!$this->isCsrfTokenValid('delete'.$user->getID(), $request->request->get('_token'))) { + $this->addFlash('error', 'csfr_invalid'); + return $this->redirectToRoute('user_settings'); + } + + //Extract the token id from the request + $token_id = $request->request->getInt('token_id'); + + $token = $entityManager->find(ApiToken::class, $token_id); + if ($token === null) { + $this->addFlash('error', 'tfa_u2f.u2f_delete.not_existing'); + return $this->redirectToRoute('user_settings'); + } + //User can only delete its own API tokens + if ($token->getUser() !== $user) { + $this->addFlash('error', 'tfa_u2f.u2f_delete.access_denied'); + return $this->redirectToRoute('user_settings'); + } + + //Do the actual deletion + $entityManager->remove($token); + $entityManager->flush(); + + $this->addFlash('success', 'api_tokens.deleted'); + return $this->redirectToRoute('user_settings'); + } } diff --git a/src/Controller/WebauthnKeyRegistrationController.php b/src/Controller/WebauthnKeyRegistrationController.php index 9329a3b9..b2c4f344 100644 --- a/src/Controller/WebauthnKeyRegistrationController.php +++ b/src/Controller/WebauthnKeyRegistrationController.php @@ -30,7 +30,7 @@ use RuntimeException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use function Symfony\Component\Translation\t; diff --git a/src/DataFixtures/APITokenFixtures.php b/src/DataFixtures/APITokenFixtures.php new file mode 100644 index 00000000..0ab0b7bb --- /dev/null +++ b/src/DataFixtures/APITokenFixtures.php @@ -0,0 +1,97 @@ +. + */ + +declare(strict_types=1); + + +namespace App\DataFixtures; + +use App\Entity\UserSystem\ApiToken; +use App\Entity\UserSystem\ApiTokenLevel; +use App\Entity\UserSystem\User; +use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Common\DataFixtures\DependentFixtureInterface; +use Doctrine\Persistence\ObjectManager; + +class APITokenFixtures extends Fixture implements DependentFixtureInterface +{ + public const TOKEN_READONLY = 'tcp_readonly'; + public const TOKEN_EDIT = 'tcp_edit'; + public const TOKEN_ADMIN = 'tcp_admin'; + public const TOKEN_FULL = 'tcp_full'; + public const TOKEN_EXPIRED = 'tcp_expired'; + + public function load(ObjectManager $manager): void + { + /** @var User $admin_user */ + $admin_user = $this->getReference(UserFixtures::ADMIN, User::class); + + $read_only_token = new ApiToken(); + $read_only_token->setUser($admin_user); + $read_only_token->setLevel(ApiTokenLevel::READ_ONLY); + $read_only_token->setName('read-only'); + $this->setTokenSecret($read_only_token, self::TOKEN_READONLY); + $manager->persist($read_only_token); + + $editor_token = new ApiToken(); + $editor_token->setUser($admin_user); + $editor_token->setLevel(ApiTokenLevel::EDIT); + $editor_token->setName('edit'); + $this->setTokenSecret($editor_token, self::TOKEN_EDIT); + $manager->persist($editor_token); + + $admin_token = new ApiToken(); + $admin_token->setUser($admin_user); + $admin_token->setLevel(ApiTokenLevel::ADMIN); + $admin_token->setName('admin'); + $this->setTokenSecret($admin_token, self::TOKEN_ADMIN); + $manager->persist($admin_token); + + $full_token = new ApiToken(); + $full_token->setUser($admin_user); + $full_token->setLevel(ApiTokenLevel::FULL); + $full_token->setName('full'); + $this->setTokenSecret($full_token, self::TOKEN_FULL); + $manager->persist($full_token); + + $expired_token = new ApiToken(); + $expired_token->setUser($admin_user); + $expired_token->setLevel(ApiTokenLevel::FULL); + $expired_token->setName('expired'); + $expired_token->setValidUntil(new \DateTimeImmutable('-1 day')); + $this->setTokenSecret($expired_token, self::TOKEN_EXPIRED); + $manager->persist($expired_token); + + $manager->flush(); + } + + private function setTokenSecret(ApiToken $token, string $secret): void + { + //Access private property + $reflection = new \ReflectionClass($token); + $property = $reflection->getProperty('token'); + $property->setValue($token, $secret); + } + + public function getDependencies(): array + { + return [UserFixtures::class]; + } +} \ No newline at end of file diff --git a/src/DataFixtures/CurrencyFixtures.php b/src/DataFixtures/CurrencyFixtures.php new file mode 100644 index 00000000..2de5b277 --- /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 a2043bdb..9c685338 100644 --- a/src/DataFixtures/DataStructureFixtures.php +++ b/src/DataFixtures/DataStructureFixtures.php @@ -24,14 +24,14 @@ namespace App\DataFixtures; use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; -use App\Entity\UserSystem\User; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\ORM\EntityManagerInterface; @@ -51,7 +51,7 @@ class DataStructureFixtures extends Fixture implements DependentFixtureInterface { //Reset autoincrement $types = [AttachmentType::class, Project::class, Category::class, Footprint::class, Manufacturer::class, - MeasurementUnit::class, Storelocation::class, Supplier::class,]; + MeasurementUnit::class, StorageLocation::class, Supplier::class, PartCustomState::class]; foreach ($types as $type) { $this->createNodesForClass($type, $manager); @@ -75,30 +75,37 @@ class DataStructureFixtures extends Fixture implements DependentFixtureInterface /** @var AbstractStructuralDBElement $node1 */ $node1 = new $class(); $node1->setName('Node 1'); + $this->addReference($class . '_1', $node1); /** @var AbstractStructuralDBElement $node2 */ $node2 = new $class(); $node2->setName('Node 2'); + $this->addReference($class . '_2', $node2); /** @var AbstractStructuralDBElement $node3 */ $node3 = new $class(); $node3->setName('Node 3'); + $this->addReference($class . '_3', $node3); $node1_1 = new $class(); $node1_1->setName('Node 1.1'); $node1_1->setParent($node1); + $this->addReference($class . '_4', $node1_1); $node1_2 = new $class(); $node1_2->setName('Node 1.2'); $node1_2->setParent($node1); + $this->addReference($class . '_5', $node1_2); $node2_1 = new $class(); $node2_1->setName('Node 2.1'); $node2_1->setParent($node2); + $this->addReference($class . '_6', $node2_1); $node1_1_1 = new $class(); $node1_1_1->setName('Node 1.1.1'); $node1_1_1->setParent($node1_1); + $this->addReference($class . '_7', $node1_1_1); $manager->persist($node1); $manager->persist($node2); diff --git a/src/DataFixtures/EDADataFixtures.php b/src/DataFixtures/EDADataFixtures.php new file mode 100644 index 00000000..1484f03e --- /dev/null +++ b/src/DataFixtures/EDADataFixtures.php @@ -0,0 +1,71 @@ +. + */ + +declare(strict_types=1); + + +namespace App\DataFixtures; + +use App\Entity\Parts\Category; +use App\Entity\Parts\Footprint; +use App\Entity\Parts\Part; +use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Common\DataFixtures\DependentFixtureInterface; +use Doctrine\Persistence\ObjectManager; + +class EDADataFixtures extends Fixture implements DependentFixtureInterface +{ + + public function getDependencies(): array + { + return [PartFixtures::class]; + } + + public function load(ObjectManager $manager): void + { + //Load elements from DB + $category1 = $manager->find(Category::class, 1); + $footprint1 = $manager->find(Footprint::class, 1); + + $part1 = $manager->find(Part::class, 1); + + //Put some data into category1 and foorprint1 + $category1?->getEdaInfo() + ->setExcludeFromBoard(true) + ->setKicadSymbol('Category:1') + ->setReferencePrefix('C') + ; + + $footprint1?->getEdaInfo() + ->setKicadFootprint('Footprint:1') + ; + + //Put some data into part1 (which overrides the data from category1 and footprint1 on part1) + $part1?->getEdaInfo() + ->setExcludeFromSim(false) + ->setKicadSymbol('Part:1') + ->setKicadFootprint('Part:1') + ->setReferencePrefix('P') + ; + + //Flush the changes + $manager->flush(); + } +} \ No newline at end of file diff --git a/src/DataFixtures/LogEntryFixtures.php b/src/DataFixtures/LogEntryFixtures.php new file mode 100644 index 00000000..eb9cf731 --- /dev/null +++ b/src/DataFixtures/LogEntryFixtures.php @@ -0,0 +1,106 @@ +. + */ + +declare(strict_types=1); + + +namespace App\DataFixtures; + +use App\Entity\LogSystem\ElementCreatedLogEntry; +use App\Entity\LogSystem\ElementDeletedLogEntry; +use App\Entity\LogSystem\ElementEditedLogEntry; +use App\Entity\Parts\Category; +use App\Entity\UserSystem\User; +use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Common\DataFixtures\DependentFixtureInterface; +use Doctrine\Persistence\ObjectManager; + +class LogEntryFixtures extends Fixture implements DependentFixtureInterface +{ + + public function load(ObjectManager $manager): void + { + $this->createCategoryEntries($manager); + $this->createDeletedCategory($manager); + } + + public function createCategoryEntries(ObjectManager $manager): void + { + $category = $this->getReference(Category::class . '_1', Category::class); + + $logEntry = new ElementCreatedLogEntry($category); + $logEntry->setTimestamp(new \DateTimeImmutable("+1 second")); + $logEntry->setUser($this->getReference(UserFixtures::ADMIN, User::class)); + $logEntry->setComment('Test'); + $manager->persist($logEntry); + + $logEntry = new ElementEditedLogEntry($category); + $logEntry->setTimestamp(new \DateTimeImmutable("+2 second")); + $logEntry->setUser($this->getReference(UserFixtures::ADMIN, User::class)); + $logEntry->setComment('Test'); + + $logEntry->setOldData(['name' => 'Test']); + $logEntry->setNewData(['name' => 'Node 1.1']); + + $manager->persist($logEntry); + $manager->flush(); + } + + public function createDeletedCategory(ObjectManager $manager): void + { + //We create a fictive category to test the deletion + $category = new Category(); + $category->setName('Node 100'); + + //Assume a category with id 100 was deleted + $reflClass = new \ReflectionClass($category); + $reflClass->getProperty('id')->setValue($category, 100); + + //The whole lifecycle from creation to deletion + $logEntry = new ElementCreatedLogEntry($category); + $logEntry->setUser($this->getReference(UserFixtures::ADMIN, User::class)); + $logEntry->setComment('Creation'); + $manager->persist($logEntry); + + $logEntry = new ElementEditedLogEntry($category); + $logEntry->setTimestamp(new \DateTimeImmutable("+1 second")); + $logEntry->setUser($this->getReference(UserFixtures::ADMIN, User::class)); + $logEntry->setComment('Edit'); + $logEntry->setOldData(['name' => 'Test']); + $logEntry->setNewData(['name' => 'Node 100']); + $manager->persist($logEntry); + + $logEntry = new ElementDeletedLogEntry($category); + $logEntry->setTimestamp(new \DateTimeImmutable("+2 second")); + $logEntry->setUser($this->getReference(UserFixtures::ADMIN, User::class)); + $logEntry->setOldData(['name' => 'Node 100', 'id' => 100, 'comment' => 'Test comment']); + $manager->persist($logEntry); + + $manager->flush(); + } + + public function getDependencies(): array + { + return [ + UserFixtures::class, + DataStructureFixtures::class + ]; + } +} \ No newline at end of file diff --git a/src/DataFixtures/PartFixtures.php b/src/DataFixtures/PartFixtures.php index 477d0dd3..a60d037d 100644 --- a/src/DataFixtures/PartFixtures.php +++ b/src/DataFixtures/PartFixtures.php @@ -49,7 +49,7 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\ManufacturingStatus; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Pricedetail; @@ -73,6 +73,7 @@ class PartFixtures extends Fixture implements DependentFixtureInterface $part = new Part(); $part->setName('Part 1'); $part->setCategory($manager->find(Category::class, 1)); + $this->addReference(Part::class . '_1', $part); $manager->persist($part); /** More complex part */ @@ -83,8 +84,10 @@ class PartFixtures extends Fixture implements DependentFixtureInterface $part->setManufacturer($manager->find(Manufacturer::class, 1)); $part->setTags('test, Test, Part2'); $part->setMass(100.2); + $part->setIpn('IPN123'); $part->setNeedsReview(true); $part->setManufacturingStatus(ManufacturingStatus::ACTIVE); + $this->addReference(Part::class . '_2', $part); $manager->persist($part); /** Part with orderdetails, storelocations and Attachments */ @@ -94,14 +97,16 @@ class PartFixtures extends Fixture implements DependentFixtureInterface $part->setCategory($manager->find(Category::class, 1)); $partLot1 = new PartLot(); $partLot1->setAmount(1.0); - $partLot1->setStorageLocation($manager->find(Storelocation::class, 1)); + $partLot1->setStorageLocation($manager->find(StorageLocation::class, 1)); $part->addPartLot($partLot1); + $partLot2 = new PartLot(); - $partLot2->setExpirationDate(new DateTime()); + $partLot2->setExpirationDate(new \DateTimeImmutable()); $partLot2->setComment('Test'); $partLot2->setNeedsRefill(true); - $partLot2->setStorageLocation($manager->find(Storelocation::class, 3)); + $partLot2->setStorageLocation($manager->find(StorageLocation::class, 3)); + $partLot2->setUserBarcode('lot2_vendor_barcode'); $part->addPartLot($partLot2); $orderdetail = new Orderdetail(); @@ -126,11 +131,13 @@ class PartFixtures extends Fixture implements DependentFixtureInterface $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); + $this->addReference(Part::class . '_3', $part); + $manager->persist($part); $manager->flush(); } diff --git a/src/DataFixtures/UserFixtures.php b/src/DataFixtures/UserFixtures.php index d34a83cd..922d0b1e 100644 --- a/src/DataFixtures/UserFixtures.php +++ b/src/DataFixtures/UserFixtures.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\DataFixtures; +use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Common\DataFixtures\DependentFixtureInterface; @@ -31,6 +32,8 @@ use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; class UserFixtures extends Fixture implements DependentFixtureInterface { + public const ADMIN = 'user-admin'; + public function __construct(protected UserPasswordHasherInterface $encoder, protected EntityManagerInterface $em) { } @@ -39,7 +42,7 @@ class UserFixtures extends Fixture implements DependentFixtureInterface { $anonymous = new User(); $anonymous->setName('anonymous'); - $anonymous->setGroup($this->getReference(GroupFixtures::READONLY)); + $anonymous->setGroup($this->getReference(GroupFixtures::READONLY, Group::class)); $anonymous->setNeedPwChange(false); $anonymous->setPassword($this->encoder->hashPassword($anonymous, 'test')); $manager->persist($anonymous); @@ -48,8 +51,9 @@ class UserFixtures extends Fixture implements DependentFixtureInterface $admin->setName('admin'); $admin->setPassword($this->encoder->hashPassword($admin, 'test')); $admin->setNeedPwChange(false); - $admin->setGroup($this->getReference(GroupFixtures::ADMINS)); + $admin->setGroup($this->getReference(GroupFixtures::ADMINS, Group::class)); $manager->persist($admin); + $this->addReference(self::ADMIN, $admin); $user = new User(); $user->setName('user'); @@ -57,7 +61,7 @@ class UserFixtures extends Fixture implements DependentFixtureInterface $user->setEmail('user@invalid.invalid'); $user->setFirstName('Test')->setLastName('User'); $user->setPassword($this->encoder->hashPassword($user, 'test')); - $user->setGroup($this->getReference(GroupFixtures::USERS)); + $user->setGroup($this->getReference(GroupFixtures::USERS, Group::class)); $manager->persist($user); $noread = new User(); diff --git a/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php b/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php index b296c4fa..ff69a69e 100644 --- a/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php +++ b/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php @@ -50,6 +50,6 @@ class CustomFetchJoinORMAdapter extends FetchJoinORMAdapter $paginator = new Paginator($qb_without_group_by); - return $paginator->count() ?? 0; + return $paginator->count(); } } diff --git a/src/DataTables/Adapters/FetchResultsAtOnceORMAdapter.php b/src/DataTables/Adapters/FetchResultsAtOnceORMAdapter.php index 182dcbda..eda48038 100644 --- a/src/DataTables/Adapters/FetchResultsAtOnceORMAdapter.php +++ b/src/DataTables/Adapters/FetchResultsAtOnceORMAdapter.php @@ -23,8 +23,6 @@ declare(strict_types=1); namespace App\DataTables\Adapters; -use App\DataTables\Events\ORMPostQueryEvent; -use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\QueryBuilder; use Omines\DataTablesBundle\Adapter\AdapterQuery; use Omines\DataTablesBundle\Adapter\Doctrine\Event\ORMAdapterQueryEvent; @@ -45,7 +43,7 @@ class FetchResultsAtOnceORMAdapter extends ORMAdapter $state = $query->getState(); // Apply definitive view state for current 'page' of the table - foreach ($state->getOrderBy() as list($column, $direction)) { + foreach ($state->getOrderBy() as [$column, $direction]) { /** @var AbstractColumn $column */ if ($column->isOrderable()) { $builder->addOrderBy($column->getOrderField(), $direction); diff --git a/src/DataTables/Adapters/TwoStepORMAdapater.php b/src/DataTables/Adapters/TwoStepORMAdapter.php similarity index 84% rename from src/DataTables/Adapters/TwoStepORMAdapater.php rename to src/DataTables/Adapters/TwoStepORMAdapter.php index 9246e6d1..51315c32 100644 --- a/src/DataTables/Adapters/TwoStepORMAdapater.php +++ b/src/DataTables/Adapters/TwoStepORMAdapter.php @@ -23,12 +23,9 @@ declare(strict_types=1); namespace App\DataTables\Adapters; -use Doctrine\ORM\AbstractQuery; +use Doctrine\ORM\Query\Expr\From; use Doctrine\ORM\Query; -use Doctrine\ORM\Query\Expr\Select; -use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\QueryBuilder; -use Doctrine\ORM\Tools\Pagination\CountOutputWalker; use Doctrine\ORM\Tools\Pagination\Paginator; use Doctrine\Persistence\ManagerRegistry; use Omines\DataTablesBundle\Adapter\AdapterQuery; @@ -49,16 +46,18 @@ use Symfony\Component\OptionsResolver\OptionsResolver; * This way we save the overhead of the fetch join query for the count and counting, which can be very slow, cause * no indexes can be used. */ -class TwoStepORMAdapater extends ORMAdapter +class TwoStepORMAdapter extends ORMAdapter { private \Closure $detailQueryCallable; private bool $use_simple_total = false; - public function __construct(ManagerRegistry $registry = null) + private \Closure|null $query_modifier = null; + + public function __construct(?ManagerRegistry $registry = null) { parent::__construct($registry); - $this->detailQueryCallable = static function (QueryBuilder $qb, array $ids) { + $this->detailQueryCallable = static function (QueryBuilder $qb, array $ids): never { throw new \RuntimeException('You need to set the detail_query option to use the TwoStepORMAdapter'); }; } @@ -68,9 +67,7 @@ class TwoStepORMAdapater extends ORMAdapter parent::configureOptions($resolver); $resolver->setRequired('filter_query'); - $resolver->setDefault('query', function (Options $options) { - return $options['filter_query']; - }); + $resolver->setDefault('query', fn(Options $options) => $options['filter_query']); $resolver->setRequired('detail_query'); $resolver->setAllowedTypes('detail_query', \Closure::class); @@ -81,6 +78,10 @@ class TwoStepORMAdapater extends ORMAdapter */ $resolver->setDefault('simple_total_query', false); + //Add the possibility of a closure to modify the query builder before the query is executed + $resolver->setDefault('query_modifier', null); + $resolver->setAllowedTypes('query_modifier', ['null', \Closure::class]); + } protected function afterConfiguration(array $options): void @@ -88,6 +89,7 @@ class TwoStepORMAdapater extends ORMAdapter parent::afterConfiguration($options); $this->detailQueryCallable = $options['detail_query']; $this->use_simple_total = $options['simple_total_query']; + $this->query_modifier = $options['query_modifier']; } protected function prepareQuery(AdapterQuery $query): void @@ -105,7 +107,7 @@ class TwoStepORMAdapater extends ORMAdapter } } - /** @var Query\Expr\From $fromClause */ + /** @var From $fromClause */ $fromClause = $builder->getDQLPart('from')[0]; $identifier = "{$fromClause->getAlias()}.{$this->metadata->getSingleIdentifierFieldName()}"; @@ -126,8 +128,18 @@ class TwoStepORMAdapater extends ORMAdapter $query->setIdentifierPropertyPath($this->mapFieldToPropertyPath($identifier, $aliases)); } + protected function hasGroupByPart(string $identifier, array $gbList): bool + { + //Always return true, to fix the issue with the count query, when having mutliple group by parts + return true; + } + protected function getCount(QueryBuilder $queryBuilder, $identifier): int { + if ($this->query_modifier !== null) { + $queryBuilder = $this->query_modifier->__invoke(clone $queryBuilder); + } + //Check if the queryBuilder is having a HAVING clause, which would make the count query invalid if (empty($queryBuilder->getDQLPart('having'))) { //If not, we can use the simple count query @@ -146,7 +158,7 @@ class TwoStepORMAdapater extends ORMAdapter $state = $query->getState(); // Apply definitive view state for current 'page' of the table - foreach ($state->getOrderBy() as list($column, $direction)) { + foreach ($state->getOrderBy() as [$column, $direction]) { /** @var AbstractColumn $column */ if ($column->isOrderable()) { $builder->addOrderBy($column->getOrderField(), $direction); @@ -159,6 +171,11 @@ class TwoStepORMAdapater extends ORMAdapter ; } + //Apply the query modifier, if set + if ($this->query_modifier !== null) { + $builder = $this->query_modifier->__invoke($builder); + } + $id_query = $builder->getQuery(); $event = new ORMAdapterQueryEvent($id_query); $state->getDataTable()->getEventDispatcher()->dispatch($event, ORMAdapterEvents::PRE_QUERY); @@ -183,7 +200,7 @@ class TwoStepORMAdapater extends ORMAdapter /** The paginator count queries can be rather slow, so when query for total count (100ms or longer), * just return the entity count. */ - /** @var Query\Expr\From $from_expr */ + /** @var From $from_expr */ $from_expr = $queryBuilder->getDQLPart('from')[0]; return $this->manager->getRepository($from_expr->getFrom())->count([]); diff --git a/src/DataTables/AttachmentDataTable.php b/src/DataTables/AttachmentDataTable.php index 62a35049..16e6a7a7 100644 --- a/src/DataTables/AttachmentDataTable.php +++ b/src/DataTables/AttachmentDataTable.php @@ -27,7 +27,6 @@ use App\DataTables\Column\PrettyBoolColumn; use App\DataTables\Column\RowClassColumn; use App\DataTables\Filters\AttachmentFilter; use App\Entity\Attachments\Attachment; -use App\Entity\LogSystem\AbstractLogEntry; use App\Services\Attachments\AttachmentManager; use App\Services\Attachments\AttachmentURLGenerator; use App\Services\ElementTypeNameGenerator; @@ -35,6 +34,7 @@ use App\Services\EntityURLGenerator; use Doctrine\ORM\QueryBuilder; use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider; use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter; +use Omines\DataTablesBundle\Column\NumberColumn; use Omines\DataTablesBundle\Column\TextColumn; use Omines\DataTablesBundle\DataTable; use Omines\DataTablesBundle\DataTableTypeInterface; @@ -50,8 +50,8 @@ final class AttachmentDataTable implements DataTableTypeInterface { $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 @@ final class AttachmentDataTable implements DataTableTypeInterface '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()).')'; @@ -85,33 +85,20 @@ final class AttachmentDataTable implements DataTableTypeInterface }, ]); + $dataTable->add('id', NumberColumn::class, [ + 'label' => $this->translator->trans('part.table.id'), + 'visible' => false, + ]); + $dataTable->add('name', TextColumn::class, [ 'label' => 'attachment.edit.name', - 'render' => function ($value, Attachment $context) { - //Link to external source - if ($context->isExternal()) { - return sprintf( - '%s', - htmlspecialchars($context->getURL()), - htmlspecialchars($value) - ); - } - - if ($this->attachmentHelper->isFileExisting($context)) { - return sprintf( - '%s', - $this->entityURLGenerator->viewURL($context), - htmlspecialchars($value) - ); - } - - return $value; - }, + 'orderField' => 'NATSORT(attachment.name)', ]); $dataTable->add('attachment_type', TextColumn::class, [ 'label' => 'attachment.table.type', 'field' => 'attachment_type.name', + 'orderField' => 'NATSORT(attachment_type.name)', 'render' => fn($value, Attachment $context): string => sprintf( '%s', $this->entityURLGenerator->editURL($context->getAttachmentType()), @@ -129,25 +116,60 @@ final class AttachmentDataTable implements DataTableTypeInterface ), ]); - $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( @@ -221,7 +243,7 @@ final class AttachmentDataTable implements DataTableTypeInterface //We do the most stuff here in the filter class if (isset($options['filter'])) { if(!$options['filter'] instanceof AttachmentFilter) { - throw new \Exception('filter must be an instance of AttachmentFilter!'); + throw new \RuntimeException('filter must be an instance of AttachmentFilter!'); } $filter = $options['filter']; diff --git a/src/DataTables/Column/EntityColumn.php b/src/DataTables/Column/EntityColumn.php index 2facdc81..54ae3fb3 100644 --- a/src/DataTables/Column/EntityColumn.php +++ b/src/DataTables/Column/EntityColumn.php @@ -42,7 +42,7 @@ class EntityColumn extends AbstractColumn * @param mixed $value The single value of the column * @return mixed */ - public function normalize($value): mixed + public function normalize(mixed $value): mixed { /** @var AbstractNamedDBElement $value */ return $value; diff --git a/src/DataTables/Column/EnumColumn.php b/src/DataTables/Column/EnumColumn.php index e41b79e4..5a5d998d 100644 --- a/src/DataTables/Column/EnumColumn.php +++ b/src/DataTables/Column/EnumColumn.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Column; use Omines\DataTablesBundle\Column\AbstractColumn; diff --git a/src/DataTables/Column/LocaleDateTimeColumn.php b/src/DataTables/Column/LocaleDateTimeColumn.php index ce8cccda..e60b2867 100644 --- a/src/DataTables/Column/LocaleDateTimeColumn.php +++ b/src/DataTables/Column/LocaleDateTimeColumn.php @@ -47,7 +47,7 @@ class LocaleDateTimeColumn extends AbstractColumn } if (!$value instanceof DateTimeInterface) { - $value = new DateTime((string) $value); + $value = new \DateTimeImmutable((string) $value); } $formatValues = [ @@ -79,10 +79,7 @@ class LocaleDateTimeColumn extends AbstractColumn ); } - /** - * @return $this - */ - protected function configureOptions(OptionsResolver $resolver): self + protected function configureOptions(OptionsResolver $resolver): static { parent::configureOptions($resolver); diff --git a/src/DataTables/Column/LogEntryTargetColumn.php b/src/DataTables/Column/LogEntryTargetColumn.php index 272ff732..7a329cd7 100644 --- a/src/DataTables/Column/LogEntryTargetColumn.php +++ b/src/DataTables/Column/LogEntryTargetColumn.php @@ -22,25 +22,9 @@ declare(strict_types=1); namespace App\DataTables\Column; -use App\Entity\Attachments\Attachment; -use App\Entity\Base\AbstractDBElement; -use App\Entity\Contracts\NamedElementInterface; -use App\Entity\LogSystem\AbstractLogEntry; -use App\Entity\LogSystem\UserNotAllowedLogEntry; -use App\Entity\Parameters\AbstractParameter; -use App\Entity\Parts\PartLot; -use App\Entity\PriceInformations\Orderdetail; -use App\Entity\PriceInformations\Pricedetail; -use App\Entity\ProjectSystem\ProjectBOMEntry; -use App\Exceptions\EntityNotSupportedException; -use App\Repository\LogEntryRepository; -use App\Services\ElementTypeNameGenerator; -use App\Services\EntityURLGenerator; use App\Services\LogSystem\LogTargetHelper; -use Doctrine\ORM\EntityManagerInterface; use Omines\DataTablesBundle\Column\AbstractColumn; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Contracts\Translation\TranslatorInterface; class LogEntryTargetColumn extends AbstractColumn { @@ -57,10 +41,7 @@ class LogEntryTargetColumn extends AbstractColumn return $value; } - /** - * @return $this - */ - public function configureOptions(OptionsResolver $resolver): self + public function configureOptions(OptionsResolver $resolver): static { parent::configureOptions($resolver); $resolver->setDefault('show_associated', true); diff --git a/src/DataTables/Column/MarkdownColumn.php b/src/DataTables/Column/MarkdownColumn.php index 9120c4c5..41f62649 100644 --- a/src/DataTables/Column/MarkdownColumn.php +++ b/src/DataTables/Column/MarkdownColumn.php @@ -35,9 +35,9 @@ class MarkdownColumn extends AbstractColumn * The normalize function is responsible for converting parsed and processed data to a datatables-appropriate type. * * @param mixed $value The single value of the column - * @return mixed + * @return string */ - public function normalize($value): mixed + public function normalize(mixed $value): string { return $this->markdown->markForRendering($value, true); } diff --git a/src/DataTables/Column/PartAttachmentsColumn.php b/src/DataTables/Column/PartAttachmentsColumn.php index 0787a1e0..7b209028 100644 --- a/src/DataTables/Column/PartAttachmentsColumn.php +++ b/src/DataTables/Column/PartAttachmentsColumn.php @@ -43,7 +43,7 @@ class PartAttachmentsColumn extends AbstractColumn * @param mixed $value The single value of the column * @return mixed */ - public function normalize($value): mixed + public function normalize(mixed $value): mixed { return $value; } @@ -79,10 +79,7 @@ class PartAttachmentsColumn extends AbstractColumn return $tmp; } - /** - * @return $this - */ - public function configureOptions(OptionsResolver $resolver): self + public function configureOptions(OptionsResolver $resolver): static { parent::configureOptions($resolver); diff --git a/src/DataTables/Column/RowClassColumn.php b/src/DataTables/Column/RowClassColumn.php index 9b7aa0a0..15bf8bf2 100644 --- a/src/DataTables/Column/RowClassColumn.php +++ b/src/DataTables/Column/RowClassColumn.php @@ -28,11 +28,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class RowClassColumn extends AbstractColumn { - - /** - * @return $this - */ - public function configureOptions(OptionsResolver $resolver): self + public function configureOptions(OptionsResolver $resolver): static { parent::configureOptions($resolver); @@ -56,7 +52,7 @@ class RowClassColumn extends AbstractColumn /** * @return mixed */ - public function normalize($value) + public function normalize($value): mixed { return $value; } diff --git a/src/DataTables/Column/SIUnitNumberColumn.php b/src/DataTables/Column/SIUnitNumberColumn.php index be50505d..b64152be 100644 --- a/src/DataTables/Column/SIUnitNumberColumn.php +++ b/src/DataTables/Column/SIUnitNumberColumn.php @@ -32,10 +32,7 @@ class SIUnitNumberColumn extends AbstractColumn { } - /** - * @return $this - */ - public function configureOptions(OptionsResolver $resolver): self + public function configureOptions(OptionsResolver $resolver): static { parent::configureOptions($resolver); diff --git a/src/DataTables/Column/SelectColumn.php b/src/DataTables/Column/SelectColumn.php index 82003a62..39445ac8 100644 --- a/src/DataTables/Column/SelectColumn.php +++ b/src/DataTables/Column/SelectColumn.php @@ -30,10 +30,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; */ class SelectColumn extends AbstractColumn { - /** - * @return $this - */ - public function configureOptions(OptionsResolver $resolver): self + public function configureOptions(OptionsResolver $resolver): static { parent::configureOptions($resolver); @@ -41,7 +38,7 @@ class SelectColumn extends AbstractColumn 'label' => '', 'orderable' => false, 'searchable' => false, - 'className' => 'select-checkbox no-colvis', + 'className' => 'dt-select no-colvis', 'visible' => true, ]); diff --git a/src/DataTables/Column/TagsColumn.php b/src/DataTables/Column/TagsColumn.php index 49ed89c7..f98a3900 100644 --- a/src/DataTables/Column/TagsColumn.php +++ b/src/DataTables/Column/TagsColumn.php @@ -37,7 +37,7 @@ class TagsColumn extends AbstractColumn * @param mixed $value The single value of the column * @return mixed */ - public function normalize($value): mixed + public function normalize(mixed $value): mixed { if (empty($value)) { return []; diff --git a/src/DataTables/ErrorDataTable.php b/src/DataTables/ErrorDataTable.php index ea3c1e76..833ea934 100644 --- a/src/DataTables/ErrorDataTable.php +++ b/src/DataTables/ErrorDataTable.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace App\DataTables; use App\DataTables\Column\RowClassColumn; -use App\Entity\Parts\Part; use Omines\DataTablesBundle\Adapter\ArrayAdapter; use Omines\DataTablesBundle\Column\TextColumn; use Omines\DataTablesBundle\DataTable; @@ -67,8 +66,10 @@ class ErrorDataTable implements DataTableTypeInterface //Build the array containing data $data = []; + $n = 0; foreach ($options['errors'] as $error) { - $data[] = ['error' => $error]; + $data['error_' . $n] = ['error' => $error]; + $n++; } $dataTable->createAdapter(ArrayAdapter::class, $data); diff --git a/src/DataTables/Filters/AttachmentFilter.php b/src/DataTables/Filters/AttachmentFilter.php index 9f8cf094..69d2aeac 100644 --- a/src/DataTables/Filters/AttachmentFilter.php +++ b/src/DataTables/Filters/AttachmentFilter.php @@ -22,6 +22,7 @@ declare(strict_types=1); */ namespace App\DataTables\Filters; +use App\DataTables\Filters\Constraints\AbstractConstraint; use App\DataTables\Filters\Constraints\BooleanConstraint; use App\DataTables\Filters\Constraints\DateTimeConstraint; use App\DataTables\Filters\Constraints\EntityConstraint; @@ -32,6 +33,7 @@ use App\DataTables\Filters\Constraints\TextConstraint; use App\Entity\Attachments\AttachmentType; use App\Services\Trees\NodesListBuilder; use Doctrine\ORM\QueryBuilder; +use Omines\DataTablesBundle\Filter\AbstractFilter; class AttachmentFilter implements FilterInterface { @@ -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 @@ class AttachmentFilter implements FilterInterface $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 cbb62352..c632b2a4 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 @@ abstract class AbstractConstraint implements FilterInterface * @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 b3f1dc47..8eb4f042 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 1ded472e..a3043170 100644 --- a/src/DataTables/Filters/Constraints/DateTimeConstraint.php +++ b/src/DataTables/Filters/Constraints/DateTimeConstraint.php @@ -22,9 +22,92 @@ declare(strict_types=1); */ namespace App\DataTables\Filters\Constraints; +use Doctrine\ORM\QueryBuilder; +use RuntimeException; + /** - * An alias of NumberConstraint to use to filter on a DateTime + * Similar to NumberConstraint but for DateTime values */ -class DateTimeConstraint extends NumberConstraint +class DateTimeConstraint extends AbstractConstraint { + protected const ALLOWED_OPERATOR_VALUES = ['=', '!=', '<', '>', '<=', '>=', 'BETWEEN']; + + public function __construct( + string $property, + ?string $identifier = null, + /** + * The value1 used for comparison (this is the main one used for all mono-value comparisons) + */ + protected \DateTimeInterface|null $value1 = null, + protected ?string $operator = null, + /** + * The second value used when operator is RANGE; this is the upper bound of the range + */ + protected \DateTimeInterface|null $value2 = null) + { + parent::__construct($property, $identifier); + } + + public function getValue1(): ?\DateTimeInterface + { + return $this->value1; + } + + public function setValue1(\DateTimeInterface|null $value1): void + { + $this->value1 = $value1; + } + + public function getValue2(): ?\DateTimeInterface + { + return $this->value2; + } + + public function setValue2(?\DateTimeInterface $value2): void + { + $this->value2 = $value2; + } + + public function getOperator(): string|null + { + return $this->operator; + } + + /** + * @param string $operator + */ + public function setOperator(?string $operator): void + { + $this->operator = $operator; + } + + public function isEnabled(): bool + { + return $this->value1 !== null + && ($this->operator !== null && $this->operator !== ''); + } + + public function apply(QueryBuilder $queryBuilder): void + { + //If no value is provided then we do not apply a filter + if (!$this->isEnabled()) { + return; + } + + //Ensure we have an valid operator + if(!in_array($this->operator, self::ALLOWED_OPERATOR_VALUES, true)) { + throw new \RuntimeException('Invalid operator '. $this->operator . ' provided. Valid operators are '. implode(', ', self::ALLOWED_OPERATOR_VALUES)); + } + + if ($this->operator !== 'BETWEEN') { + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, $this->operator, $this->value1); + } else { + if ($this->value2 === null) { + throw new RuntimeException("Cannot use operator BETWEEN without value2!"); + } + + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '1', '>=', $this->value1); + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '2', '<=', $this->value2); + } + } } diff --git a/src/DataTables/Filters/Constraints/EntityConstraint.php b/src/DataTables/Filters/Constraints/EntityConstraint.php index 7365890b..c75da80d 100644 --- a/src/DataTables/Filters/Constraints/EntityConstraint.php +++ b/src/DataTables/Filters/Constraints/EntityConstraint.php @@ -40,14 +40,14 @@ class EntityConstraint extends AbstractConstraint * @param class-string $class * @param string $property * @param string|null $identifier - * @param null|T $value + * @param T|null $value * @param string|null $operator */ public function __construct(protected ?NodesListBuilder $nodesListBuilder, protected string $class, string $property, - string $identifier = null, - protected $value = null, + ?string $identifier = null, + protected ?AbstractDBElement $value = null, protected ?string $operator = null) { if (!$nodesListBuilder instanceof NodesListBuilder && $this->isStructural()) { @@ -137,7 +137,7 @@ class EntityConstraint extends AbstractConstraint } //We need to handle null values differently, as they can not be compared with == or != - if (!$this->value instanceof AbstractDBElement) { + if ($this->value === null) { if($this->operator === '=' || $this->operator === 'INCLUDING_CHILDREN') { $queryBuilder->andWhere(sprintf("%s IS NULL", $this->property)); return; @@ -152,8 +152,9 @@ class EntityConstraint extends AbstractConstraint } if($this->operator === '=' || $this->operator === '!=') { - $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, $this->operator, $this->value); - return; + //Include null values on != operator, so that really all values are returned that are not equal to the given value + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, $this->operator, $this->value, $this->operator === '!='); + return; } //Otherwise retrieve the children list and apply the operator to it @@ -168,7 +169,8 @@ class EntityConstraint extends AbstractConstraint } if ($this->operator === 'EXCLUDING_CHILDREN') { - $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, 'NOT IN', $list); + //Include null values in the result, so that all elements that are not in the list are returned + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, 'NOT IN', $list, true); return; } } else { diff --git a/src/DataTables/Filters/Constraints/FilterTrait.php b/src/DataTables/Filters/Constraints/FilterTrait.php index 26707841..2932914a 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,14 +51,30 @@ trait FilterTrait { //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; } /** * Adds a simple constraint in the form of (property OPERATOR value) (e.g. "part.name = :name") to the given query builder. + * @param QueryBuilder $queryBuilder The query builder to add the constraint to + * @param string $property The property to compare + * @param string $parameterIdentifier The identifier for the parameter + * @param string $comparison_operator The comparison operator to use + * @param mixed $value The value to compare to + * @param bool $include_null If true, the result of this constraint will also include null values of this property (useful for exclusion filters) */ - protected function addSimpleAndConstraint(QueryBuilder $queryBuilder, string $property, string $parameterIdentifier, string $comparison_operator, mixed $value): void + protected function addSimpleAndConstraint(QueryBuilder $queryBuilder, string $property, string $parameterIdentifier, string $comparison_operator, mixed $value, bool $include_null = false): void { if ($comparison_operator === 'IN' || $comparison_operator === 'NOT IN') { $expression = sprintf("%s %s (:%s)", $property, $comparison_operator, $parameterIdentifier); @@ -65,6 +82,10 @@ trait FilterTrait $expression = sprintf("%s %s :%s", $property, $comparison_operator, $parameterIdentifier); } + if ($include_null) { + $expression = sprintf("(%s OR %s IS NULL)", $expression, $property); + } + if($this->useHaving || $this->isAggregateFunctionString($property)) { //If the property is an aggregate function, we have to use the "having" instead of the "where" $queryBuilder->andHaving($expression); } else { diff --git a/src/DataTables/Filters/Constraints/NumberConstraint.php b/src/DataTables/Filters/Constraints/NumberConstraint.php index 9e53b8f3..dc7cf733 100644 --- a/src/DataTables/Filters/Constraints/NumberConstraint.php +++ b/src/DataTables/Filters/Constraints/NumberConstraint.php @@ -29,12 +29,28 @@ class NumberConstraint extends AbstractConstraint { protected const ALLOWED_OPERATOR_VALUES = ['=', '!=', '<', '>', '<=', '>=', 'BETWEEN']; - public function getValue1(): float|int|null|\DateTimeInterface + public function __construct( + string $property, + ?string $identifier = null, + /** + * The value1 used for comparison (this is the main one used for all mono-value comparisons) + */ + protected float|int|null $value1 = null, + protected ?string $operator = null, + /** + * The second value used when operator is RANGE; this is the upper bound of the range + */ + protected float|int|null $value2 = null) + { + parent::__construct($property, $identifier); + } + + public function getValue1(): float|int|null { return $this->value1; } - public function setValue1(float|int|\DateTimeInterface|null $value1): void + public function setValue1(float|int|null $value1): void { $this->value1 = $value1; } @@ -63,22 +79,6 @@ class NumberConstraint extends AbstractConstraint } - public function __construct( - string $property, - string $identifier = null, - /** - * The value1 used for comparison (this is the main one used for all mono-value comparisons) - */ - protected float|int|\DateTimeInterface|null $value1 = null, - protected ?string $operator = null, - /** - * The second value used when operator is RANGE; this is the upper bound of the range - */ - protected float|int|\DateTimeInterface|null $value2 = null) - { - parent::__construct($property, $identifier); - } - public function isEnabled(): bool { return $this->value1 !== null @@ -105,7 +105,13 @@ class NumberConstraint extends AbstractConstraint } $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '1', '>=', $this->value1); - $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '2', '<=', $this->value2); + + //Workaround for the amountSum which we need to add twice on postgres. Replace one of the __ with __2 to make it work + //Otherwise we get an error, that __partLot was already defined + + $property2 = str_replace('__', '__2', $this->property); + + $this->addSimpleAndConstraint($queryBuilder, $property2, $this->identifier . '2', '<=', $this->value2); } } } diff --git a/src/DataTables/Filters/Constraints/Part/BulkImportJobExistsConstraint.php b/src/DataTables/Filters/Constraints/Part/BulkImportJobExistsConstraint.php new file mode 100644 index 00000000..9d21dd58 --- /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 00000000..d9451577 --- /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 00000000..7656a290 --- /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 eed37b8b..011824e5 100644 --- a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php @@ -23,13 +23,20 @@ declare(strict_types=1); namespace App\DataTables\Filters\Constraints\Part; use App\DataTables\Filters\Constraints\BooleanConstraint; +use App\Entity\Parts\PartLot; use Doctrine\ORM\QueryBuilder; class LessThanDesiredConstraint extends BooleanConstraint { - public function __construct(string $property = null, string $identifier = null, ?bool $default_value = null) + public function __construct(?string $property = null, ?string $identifier = null, ?bool $default_value = null) { - parent::__construct($property ?? 'amountSum', $identifier, $default_value); + parent::__construct($property ?? '( + SELECT COALESCE(SUM(ld_partLot.amount), 0.0) + FROM '.PartLot::class.' ld_partLot + WHERE ld_partLot.part = part.id + AND ld_partLot.instock_unknown = false + AND (ld_partLot.expiration_date IS NULL OR ld_partLot.expiration_date > CURRENT_DATE()) + )', $identifier ?? 'amountSumLessThanDesired', $default_value); } public function apply(QueryBuilder $queryBuilder): void @@ -41,9 +48,9 @@ class LessThanDesiredConstraint extends BooleanConstraint //If value is true, we want to filter for parts with stock < desired stock if ($this->value) { - $queryBuilder->andHaving('amountSum < minamount'); + $queryBuilder->andHaving( $this->property . ' < part.minamount'); } else { - $queryBuilder->andHaving('amountSum >= minamount'); + $queryBuilder->andHaving($this->property . ' >= part.minamount'); } } } diff --git a/src/DataTables/Filters/Constraints/Part/TagsConstraint.php b/src/DataTables/Filters/Constraints/Part/TagsConstraint.php index acd04745..2b28e6b4 100644 --- a/src/DataTables/Filters/Constraints/Part/TagsConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/TagsConstraint.php @@ -24,23 +24,15 @@ namespace App\DataTables\Filters\Constraints\Part; use Doctrine\ORM\Query\Expr\Orx; use App\DataTables\Filters\Constraints\AbstractConstraint; -use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; class TagsConstraint extends AbstractConstraint { final public const ALLOWED_OPERATOR_VALUES = ['ANY', 'ALL', 'NONE']; - /** - * @param string $value - */ - public function __construct(string $property, string $identifier = null, /** - * @var string The value to compare to - */ - protected $value = null, /** - * @var string|null The operator to use - */ - protected ?string $operator = '') + public function __construct(string $property, ?string $identifier = null, + protected ?string $value = null, + protected ?string $operator = '') { parent::__construct($property, $identifier); } @@ -62,12 +54,12 @@ class TagsConstraint extends AbstractConstraint return $this; } - public function getValue(): string + public function getValue(): ?string { return $this->value; } - public function setValue(string $value): self + public function setValue(?string $value): self { $this->value = $value; return $this; @@ -93,15 +85,18 @@ class TagsConstraint extends AbstractConstraint */ protected function getExpressionForTag(QueryBuilder $queryBuilder, string $tag): Orx { - $tag_identifier_prefix = uniqid($this->identifier . '_', false); + //Escape any %, _ or \ in the tag + $tag = addcslashes($tag, '%_\\'); + + $tag_identifier_prefix = $this->generateParameterIdentifier('tag'); $expr = $queryBuilder->expr(); $tmp = $expr->orX( - $expr->like($this->property, ':' . $tag_identifier_prefix . '_1'), - $expr->like($this->property, ':' . $tag_identifier_prefix . '_2'), - $expr->like($this->property, ':' . $tag_identifier_prefix . '_3'), - $expr->eq($this->property, ':' . $tag_identifier_prefix . '_4'), + 'ILIKE(' . $this->property . ', :' . $tag_identifier_prefix . '_1) = TRUE', + 'ILIKE(' . $this->property . ', :' . $tag_identifier_prefix . '_2) = TRUE', + 'ILIKE(' . $this->property . ', :' . $tag_identifier_prefix . '_3) = TRUE', + 'ILIKE(' . $this->property . ', :' . $tag_identifier_prefix . '_4) = TRUE', ); //Set the parameters for the LIKE expression, in each variation of the tag (so with a comma, at the end, at the beginning, and on both ends, and equaling the tag) @@ -138,6 +133,7 @@ class TagsConstraint extends AbstractConstraint return; } + //@phpstan-ignore-next-line Keep this check to ensure that everything has the same structure even if we add a new operator if ($this->operator === 'NONE') { $queryBuilder->andWhere($queryBuilder->expr()->not($queryBuilder->expr()->orX(...$tagsExpressions))); return; diff --git a/src/DataTables/Filters/Constraints/TextConstraint.php b/src/DataTables/Filters/Constraints/TextConstraint.php index 60f83328..c6a6fe19 100644 --- a/src/DataTables/Filters/Constraints/TextConstraint.php +++ b/src/DataTables/Filters/Constraints/TextConstraint.php @@ -32,10 +32,10 @@ class TextConstraint extends AbstractConstraint /** * @param string $value */ - public function __construct(string $property, string $identifier = null, /** - * @var string The value to compare to + public function __construct(string $property, ?string $identifier = null, /** + * @var string|null The value to compare to */ - protected $value = null, /** + protected ?string $value = null, /** * @var string|null The operator to use */ protected ?string $operator = '') @@ -60,12 +60,12 @@ class TextConstraint extends AbstractConstraint return $this; } - public function getValue(): string + public function getValue(): ?string { return $this->value; } - public function setValue(string $value): self + public function setValue(?string $value): self { $this->value = $value; return $this; @@ -96,24 +96,26 @@ class TextConstraint extends AbstractConstraint //The CONTAINS, LIKE, STARTS and ENDS operators use the LIKE operator, but we have to build the value string differently $like_value = null; + $escaped_value = str_replace(['%', '_'], ['\%', '\_'], $this->value); if ($this->operator === 'LIKE') { - $like_value = $this->value; + $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) { - $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, 'LIKE', $like_value); + $queryBuilder->andWhere(sprintf('ILIKE(%s, :%s) = TRUE', $this->property, $this->identifier)); + $queryBuilder->setParameter($this->identifier, $like_value); return; } //Regex is only supported on MySQL and needs a special function if ($this->operator === 'REGEX') { - $queryBuilder->andWhere(sprintf('REGEXP(%s, :%s) = 1', $this->property, $this->identifier)); + $queryBuilder->andWhere(sprintf('REGEXP(%s, :%s) = TRUE', $this->property, $this->identifier)); $queryBuilder->setParameter($this->identifier, $this->value); } } diff --git a/src/DataTables/Filters/LogFilter.php b/src/DataTables/Filters/LogFilter.php index a3bbd851..38dc2191 100644 --- a/src/DataTables/Filters/LogFilter.php +++ b/src/DataTables/Filters/LogFilter.php @@ -22,12 +22,12 @@ declare(strict_types=1); */ namespace App\DataTables\Filters; +use App\DataTables\Filters\Constraints\AbstractConstraint; use App\DataTables\Filters\Constraints\ChoiceConstraint; use App\DataTables\Filters\Constraints\DateTimeConstraint; use App\DataTables\Filters\Constraints\EntityConstraint; use App\DataTables\Filters\Constraints\InstanceOfConstraint; use App\DataTables\Filters\Constraints\IntConstraint; -use App\DataTables\Filters\Constraints\NumberConstraint; use App\Entity\UserSystem\User; use Doctrine\ORM\QueryBuilder; @@ -45,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 3d03f00c..cf185dfd 100644 --- a/src/DataTables/Filters/PartFilter.php +++ b/src/DataTables/Filters/PartFilter.php @@ -22,12 +22,16 @@ declare(strict_types=1); */ namespace App\DataTables\Filters; +use App\DataTables\Filters\Constraints\AbstractConstraint; use App\DataTables\Filters\Constraints\BooleanConstraint; use App\DataTables\Filters\Constraints\ChoiceConstraint; use App\DataTables\Filters\Constraints\DateTimeConstraint; use App\DataTables\Filters\Constraints\EntityConstraint; use App\DataTables\Filters\Constraints\IntConstraint; use App\DataTables\Filters\Constraints\NumberConstraint; +use App\DataTables\Filters\Constraints\Part\BulkImportJobExistsConstraint; +use App\DataTables\Filters\Constraints\Part\BulkImportJobStatusConstraint; +use App\DataTables\Filters\Constraints\Part\BulkImportPartStatusConstraint; use App\DataTables\Filters\Constraints\Part\LessThanDesiredConstraint; use App\DataTables\Filters\Constraints\Part\ParameterConstraint; use App\DataTables\Filters\Constraints\Part\TagsConstraint; @@ -37,8 +41,11 @@ use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\PartCustomState; +use App\Entity\Parts\PartLot; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; +use App\Entity\ProjectSystem\Project; use App\Entity\UserSystem\User; use App\Services\Trees\NodesListBuilder; use Doctrine\Common\Collections\ArrayCollection; @@ -80,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; @@ -90,8 +98,28 @@ class PartFilter implements FilterInterface public readonly ArrayCollection $parameters; public readonly IntConstraint $parametersCount; + /************************************************* + * Project tab + *************************************************/ + + public readonly EntityConstraint $project; + public readonly NumberConstraint $bomQuantity; + 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'); @@ -102,6 +130,7 @@ class PartFilter implements FilterInterface $this->favorite = new BooleanConstraint('part.favorite'); $this->needsReview = new BooleanConstraint('part.needs_review'); $this->measurementUnit = new EntityConstraint($nodesListBuilder, MeasurementUnit::class, 'part.partUnit'); + $this->partCustomState = new EntityConstraint($nodesListBuilder, PartCustomState::class, 'part.partCustomState'); $this->mass = new NumberConstraint('part.mass'); $this->dbId = new IntConstraint('part.id'); $this->ipn = new TextConstraint('part.ipn'); @@ -113,33 +142,49 @@ class PartFilter implements FilterInterface This seems to be related to the fact, that PDO does not have an float parameter type and using string type does not work in this situation (at least in SQLite) TODO: Find a better solution here */ - //We have to use Having here, as we use an alias column which is not supported on the where clause and would result in an error - $this->amountSum = (new IntConstraint('amountSum'))->useHaving(); - $this->lotCount = new IntConstraint('COUNT(partLots)'); + $this->amountSum = (new IntConstraint('( + SELECT COALESCE(SUM(__partLot.amount), 0.0) + FROM ' . PartLot::class . ' __partLot + WHERE __partLot.part = part.id + AND __partLot.instock_unknown = false + AND (__partLot.expiration_date IS NULL OR __partLot.expiration_date > CURRENT_DATE()) + )', identifier: "amountSumWhere")); + $this->lotCount = new IntConstraint('COUNT(_partLots)'); $this->lessThanDesired = new LessThanDesiredConstraint(); - $this->storelocation = new EntityConstraint($nodesListBuilder, Storelocation::class, 'partLots.storage_location'); - $this->lotNeedsRefill = new BooleanConstraint('partLots.needs_refill'); - $this->lotUnknownAmount = new BooleanConstraint('partLots.instock_unknown'); - $this->lotExpirationDate = new DateTimeConstraint('partLots.expiration_date'); - $this->lotDescription = new TextConstraint('partLots.description'); - $this->lotOwner = new EntityConstraint($nodesListBuilder, User::class, 'partLots.owner'); + $this->storelocation = new EntityConstraint($nodesListBuilder, StorageLocation::class, '_partLots.storage_location'); + $this->lotNeedsRefill = new BooleanConstraint('_partLots.needs_refill'); + $this->lotUnknownAmount = new BooleanConstraint('_partLots.instock_unknown'); + $this->lotExpirationDate = new DateTimeConstraint('_partLots.expiration_date'); + $this->lotDescription = new TextConstraint('_partLots.description'); + $this->lotOwner = new EntityConstraint($nodesListBuilder, User::class, '_partLots.owner'); $this->manufacturer = new EntityConstraint($nodesListBuilder, Manufacturer::class, 'part.manufacturer'); $this->manufacturer_product_number = new TextConstraint('part.manufacturer_product_number'); $this->manufacturer_product_url = new TextConstraint('part.manufacturer_product_url'); $this->manufacturing_status = new ChoiceConstraint('part.manufacturing_status'); - $this->attachmentsCount = new IntConstraint('COUNT(attachments)'); - $this->attachmentType = new EntityConstraint($nodesListBuilder, AttachmentType::class, 'attachments.attachment_type'); - $this->attachmentName = new TextConstraint('attachments.name'); + $this->attachmentsCount = new IntConstraint('COUNT(_attachments)'); + $this->attachmentType = new EntityConstraint($nodesListBuilder, AttachmentType::class, '_attachments.attachment_type'); + $this->attachmentName = new TextConstraint('_attachments.name'); - $this->supplier = new EntityConstraint($nodesListBuilder, Supplier::class, 'orderdetails.supplier'); - $this->orderdetailsCount = new IntConstraint('COUNT(orderdetails)'); - $this->obsolete = new BooleanConstraint('orderdetails.obsolete'); + $this->supplier = new EntityConstraint($nodesListBuilder, Supplier::class, '_orderdetails.supplier'); + $this->orderdetailsCount = new IntConstraint('COUNT(_orderdetails)'); + $this->obsolete = new BooleanConstraint('_orderdetails.obsolete'); $this->parameters = new ArrayCollection(); - $this->parametersCount = new IntConstraint('COUNT(parameters)'); + $this->parametersCount = new IntConstraint('COUNT(_parameters)'); + + $this->project = new EntityConstraint($nodesListBuilder, Project::class, '_projectBomEntries.project'); + $this->bomQuantity = new NumberConstraint('_projectBomEntries.quantity'); + $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 b94d805a..aa8c20f4 100644 --- a/src/DataTables/Filters/PartSearchFilter.php +++ b/src/DataTables/Filters/PartSearchFilter.php @@ -21,8 +21,7 @@ declare(strict_types=1); * along with this program. If not, see . */ namespace App\DataTables\Filters; - -use Doctrine\ORM\Query\Expr; +use App\DataTables\Filters\Constraints\AbstractConstraint; use Doctrine\ORM\QueryBuilder; class PartSearchFilter implements FilterInterface @@ -82,7 +81,7 @@ class PartSearchFilter implements FilterInterface $fields_to_search[] = 'part.name'; } if($this->category) { - $fields_to_search[] = 'category.name'; + $fields_to_search[] = '_category.name'; } if($this->description) { $fields_to_search[] = 'part.description'; @@ -94,22 +93,22 @@ class PartSearchFilter implements FilterInterface $fields_to_search[] = 'part.tags'; } if($this->storelocation) { - $fields_to_search[] = 'storelocations.name'; + $fields_to_search[] = '_storelocations.name'; } if($this->ordernr) { - $fields_to_search[] = 'orderdetails.supplierpartnr'; + $fields_to_search[] = '_orderdetails.supplierpartnr'; } if($this->mpn) { $fields_to_search[] = 'part.manufacturer_product_number'; } if($this->supplier) { - $fields_to_search[] = 'suppliers.name'; + $fields_to_search[] = '_suppliers.name'; } if($this->manufacturer) { - $fields_to_search[] = 'manufacturer.name'; + $fields_to_search[] = '_manufacturer.name'; } if($this->footprint) { - $fields_to_search[] = 'footprint.name'; + $fields_to_search[] = '_footprint.name'; } if ($this->ipn) { $fields_to_search[] = 'part.ipn'; @@ -130,21 +129,23 @@ class PartSearchFilter implements FilterInterface //Convert the fields to search to a list of expressions $expressions = array_map(function (string $field): string { if ($this->regex) { - return sprintf("REGEXP(%s, :search_query) = 1", $field); + return sprintf("REGEXP(%s, :search_query) = TRUE", $field); } - return sprintf("%s LIKE :search_query", $field); + return sprintf("ILIKE(%s, :search_query) = TRUE", $field); }, $fields_to_search); - //Add Or concatation of the expressions to our query + //Add Or concatenation of the expressions to our query $queryBuilder->andWhere( $queryBuilder->expr()->orX(...$expressions) ); - //For regex we pass the query as is, for like we add % to the start and end as wildcards + //For regex, we pass the query as is, for like we add % to the start and end as wildcards 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 new file mode 100644 index 00000000..b7b5b567 --- /dev/null +++ b/src/DataTables/Helpers/ColumnSortHelper.php @@ -0,0 +1,139 @@ +. + */ + +declare(strict_types=1); + + +namespace App\DataTables\Helpers; + +use Omines\DataTablesBundle\DataTable; +use Psr\Log\LoggerInterface; + +class ColumnSortHelper +{ + private array $columns = []; + + public function __construct(private readonly LoggerInterface $logger) + { + } + + /** + * Add a new column which can be sorted and visibility controlled by the user. The basic syntax is similar to + * the DataTable add method, but with additional options. + * @param string $name + * @param string $type + * @param array $options + * @param string|null $alias If an alias is set here, the column will be available under this alias in the config + * string instead of the name. + * @param bool $visibility_configurable If set to false, this column can not be visibility controlled by the user + * @return $this + */ + public function add(string $name, string $type, array $options = [], ?string $alias = null, + bool $visibility_configurable = true): self + { + //Alias allows us to override the name of the column in the env variable + $this->columns[$alias ?? $name] = [ + 'name' => $name, + 'type' => $type, + 'options' => $options, + 'visibility_configurable' => $visibility_configurable + ]; + + return $this; + } + + /** + * Remove all columns saved inside this helper + * @return void + */ + public function reset(): void + { + $this->columns = []; + } + + /** + * 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. 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, + string $config_var_name): void + { + //If the config is given as a string, convert it to an array first + if (!is_array($visible_columns)) { + $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 + foreach ($this->columns as $col_id => $col_data) { + if (!$col_data['visibility_configurable']) { + $this->addColumnEntry($dataTable, $this->columns[$col_id], null); + $processed_columns[] = $col_id; + } + } + + //Afterwards the columns, which should be visible by default + foreach ($visible_columns as $col_id) { + if (!isset($this->columns[$col_id]) || !$this->columns[$col_id]['visibility_configurable']) { + $this->logger->warning("Configuration option $config_var_name specify invalid column '$col_id'. Column is skipped."); + continue; + } + + if (in_array($col_id, $processed_columns, true)) { + $this->logger->warning("Configuration option $config_var_name specify column '$col_id' multiple time. Only first occurrence is used."); + continue; + } + $this->addColumnEntry($dataTable, $this->columns[$col_id], true); + $processed_columns[] = $col_id; + } + + //and the remaining non-visible columns + foreach (array_keys($this->columns) as $col_id) { + if (in_array($col_id, $processed_columns, true)) { + // column already processed + continue; + } + $this->addColumnEntry($dataTable, $this->columns[$col_id], false); + $processed_columns[] = $col_id; + } + } + + private function addColumnEntry(DataTable $dataTable, array $entry, ?bool $visible): void + { + $options = $entry['options'] ?? []; + if (!is_null($visible)) { + $options["visible"] = $visible; + } + $dataTable->add($entry['name'], $entry['type'], $options); + } +} \ No newline at end of file diff --git a/src/DataTables/Helpers/PartDataTableHelper.php b/src/DataTables/Helpers/PartDataTableHelper.php index 8499e303..c33c3a82 100644 --- a/src/DataTables/Helpers/PartDataTableHelper.php +++ b/src/DataTables/Helpers/PartDataTableHelper.php @@ -20,14 +20,17 @@ declare(strict_types=1); * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + namespace App\DataTables\Helpers; +use App\Entity\Parts\StorageLocation; use App\Entity\ProjectSystem\Project; use App\Entity\Attachments\Attachment; use App\Entity\Parts\Part; use App\Services\Attachments\AttachmentURLGenerator; use App\Services\Attachments\PartPreviewGenerator; use App\Services\EntityURLGenerator; +use App\Services\Formatters\AmountFormatter; use Symfony\Contracts\Translation\TranslatorInterface; /** @@ -35,8 +38,13 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class PartDataTableHelper { - public function __construct(private readonly PartPreviewGenerator $previewGenerator, private readonly AttachmentURLGenerator $attachmentURLGenerator, private readonly EntityURLGenerator $entityURLGenerator, private readonly TranslatorInterface $translator) - { + public function __construct( + private readonly PartPreviewGenerator $previewGenerator, + private readonly AttachmentURLGenerator $attachmentURLGenerator, + private readonly EntityURLGenerator $entityURLGenerator, + private readonly TranslatorInterface $translator, + private readonly AmountFormatter $amountFormatter, + ) { } public function renderName(Part $context): string @@ -45,14 +53,16 @@ class PartDataTableHelper //Depending on the part status we show a different icon (the later conditions have higher priority) if ($context->isFavorite()) { - $icon = sprintf('', $this->translator->trans('part.favorite.badge')); + $icon = sprintf('', + $this->translator->trans('part.favorite.badge')); } if ($context->isNeedsReview()) { - $icon = sprintf('', $this->translator->trans('part.needs_review.badge')); + $icon = sprintf('', + $this->translator->trans('part.needs_review.badge')); } if ($context->getBuiltProject() instanceof Project) { $icon = sprintf('', - $this->translator->trans('part.info.projectBuildPart.hint') . ': ' . $context->getBuiltProject()->getName()); + $this->translator->trans('part.info.projectBuildPart.hint').': '.$context->getBuiltProject()->getName()); } @@ -85,4 +95,62 @@ class PartDataTableHelper $title ); } + + public function renderStorageLocations(Part $context): string + { + $tmp = []; + foreach ($context->getPartLots() as $lot) { + //Ignore lots without storelocation + if (!$lot->getStorageLocation() instanceof StorageLocation) { + continue; + } + $tmp[] = sprintf( + '%s', + $this->entityURLGenerator->listPartsURL($lot->getStorageLocation()), + htmlspecialchars($lot->getStorageLocation()->getFullPath()), + htmlspecialchars($lot->getStorageLocation()->getName()) + ); + } + + return implode('
    ', $tmp); + } + + public function renderAmount(Part $context): string + { + $amount = $context->getAmountSum(); + $expiredAmount = $context->getExpiredAmountSum(); + + $ret = ''; + + if ($context->isAmountUnknown()) { + //When all amounts are unknown, we show a question mark + if ($amount === 0.0) { + $ret .= sprintf('?', + $this->translator->trans('part_lots.instock_unknown')); + } else { //Otherwise mark it with greater equal and the (known) amount + $ret .= sprintf('', + $this->translator->trans('part_lots.instock_unknown') + ); + $ret .= htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit())); + } + } else { + $ret .= htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit())); + } + + //If we have expired lots, we show them in parentheses behind + if ($expiredAmount > 0) { + $ret .= sprintf(' (+%s)', + $this->translator->trans('part_lots.is_expired'), + htmlspecialchars($this->amountFormatter->format($expiredAmount, $context->getPartUnit()))); + } + + //When the amount is below the minimum amount, we highlight the number red + if ($context->isNotEnoughInstock()) { + $ret = sprintf('%s', + $this->translator->trans('part.info.amount.less_than_desired'), + $ret); + } + + return $ret; + } } diff --git a/src/DataTables/LogDataTable.php b/src/DataTables/LogDataTable.php index c00b7446..f6604279 100644 --- a/src/DataTables/LogDataTable.php +++ b/src/DataTables/LogDataTable.php @@ -40,7 +40,6 @@ use App\Entity\LogSystem\ElementCreatedLogEntry; use App\Entity\LogSystem\ElementDeletedLogEntry; use App\Entity\LogSystem\ElementEditedLogEntry; use App\Entity\LogSystem\PartStockChangedLogEntry; -use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use App\Exceptions\EntityNotSupportedException; use App\Repository\LogEntryRepository; @@ -55,7 +54,6 @@ use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter; use Omines\DataTablesBundle\Column\TextColumn; use Omines\DataTablesBundle\DataTable; use Omines\DataTablesBundle\DataTableTypeInterface; -use Psr\Log\LogLevel; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -139,7 +137,7 @@ class LogDataTable implements DataTableTypeInterface if ($context instanceof PartStockChangedLogEntry) { $text .= sprintf( ' (%s)', - $this->translator->trans('log.part_stock_changed.' . $context->getInstockChangeType()->toExtraShortType()) + $this->translator->trans($context->getInstockChangeType()->toTranslationKey()) ); } @@ -156,7 +154,7 @@ class LogDataTable implements DataTableTypeInterface $dataTable->add('user', TextColumn::class, [ 'label' => 'log.user', - 'orderField' => 'user.name', + 'orderField' => 'NATSORT(user.name)', 'render' => function ($value, AbstractLogEntry $context): string { $user = $context->getUser(); @@ -164,7 +162,7 @@ class LogDataTable implements DataTableTypeInterface if (!$user instanceof User) { if ($context->isCLIEntry()) { return sprintf('%s [%s]', - htmlentities($context->getCLIUsername()), + htmlentities((string) $context->getCLIUsername()), $this->translator->trans('log.cli_user') ); } diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index eac739f8..0baee630 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -22,20 +22,9 @@ declare(strict_types=1); namespace App\DataTables; -use App\DataTables\Adapters\FetchResultsAtOnceORMAdapter; -use App\DataTables\Adapters\TwoStepORMAdapater; -use App\DataTables\Column\EnumColumn; -use App\Doctrine\Helpers\FieldHelper; -use App\Entity\Parts\ManufacturingStatus; -use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Mapping\ClassMetadataInfo; -use Doctrine\ORM\Query; -use Doctrine\ORM\Tools\Pagination\Paginator; -use Omines\DataTablesBundle\Adapter\Doctrine\Event\ORMAdapterQueryEvent; -use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapterEvents; -use Symfony\Bundle\SecurityBundle\Security; -use App\Entity\Parts\Storelocation; +use App\DataTables\Adapters\TwoStepORMAdapter; use App\DataTables\Column\EntityColumn; +use App\DataTables\Column\EnumColumn; use App\DataTables\Column\IconLinkColumn; use App\DataTables\Column\LocaleDateTimeColumn; use App\DataTables\Column\MarkdownColumn; @@ -47,23 +36,39 @@ use App\DataTables\Column\SIUnitNumberColumn; use App\DataTables\Column\TagsColumn; use App\DataTables\Filters\PartFilter; use App\DataTables\Filters\PartSearchFilter; +use App\DataTables\Helpers\ColumnSortHelper; use App\DataTables\Helpers\PartDataTableHelper; +use App\Doctrine\Helpers\FieldHelper; +use App\Entity\Parts\ManufacturingStatus; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Services\Formatters\AmountFormatter; +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; use Omines\DataTablesBundle\Column\TextColumn; use Omines\DataTablesBundle\DataTable; use Omines\DataTablesBundle\DataTableTypeInterface; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Contracts\Translation\TranslatorInterface; final class PartsDataTable implements DataTableTypeInterface { - public function __construct(private readonly EntityURLGenerator $urlGenerator, private readonly TranslatorInterface $translator, private readonly AmountFormatter $amountFormatter, private readonly PartDataTableHelper $partDataTableHelper, private readonly Security $security) - { + 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 ColumnSortHelper $csh, + private readonly TableSettings $tableSettings, + ) { } public function configureOptions(OptionsResolver $optionsResolver): void @@ -83,9 +88,9 @@ final class PartsDataTable implements DataTableTypeInterface $this->configureOptions($resolver); $options = $resolver->resolve($options); - $dataTable + $this->csh //Color the table rows depending on the review and favorite status - ->add('dont_matter', RowClassColumn::class, [ + ->add('row_color', RowClassColumn::class, [ 'render' => function ($value, Part $context): string { if ($context->isNeedsReview()) { return 'table-secondary'; @@ -96,193 +101,186 @@ final class PartsDataTable implements DataTableTypeInterface return ''; //Default coloring otherwise }, - ]) - - ->add('select', SelectColumn::class) + ], visibility_configurable: false) + ->add('select', SelectColumn::class, visibility_configurable: false) ->add('picture', TextColumn::class, [ 'label' => '', 'className' => 'no-colvis', 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderPicture($context), - ]) + ], visibility_configurable: false) ->add('name', TextColumn::class, [ 'label' => $this->translator->trans('part.table.name'), 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderName($context), + 'orderField' => 'NATSORT(part.name)' ]) ->add('id', TextColumn::class, [ 'label' => $this->translator->trans('part.table.id'), - 'visible' => false, ]) ->add('ipn', TextColumn::class, [ 'label' => $this->translator->trans('part.table.ipn'), - 'visible' => false, + 'orderField' => 'NATSORT(part.ipn)' ]) ->add('description', MarkdownColumn::class, [ 'label' => $this->translator->trans('part.table.description'), - ]); - - if ($this->security->isGranted('@categories.read')) { - $dataTable->add('category', EntityColumn::class, [ + ]) + ->add('category', EntityColumn::class, [ 'label' => $this->translator->trans('part.table.category'), 'property' => 'category', - ]); - } - - if ($this->security->isGranted('@footprints.read')) { - $dataTable->add('footprint', EntityColumn::class, [ + 'orderField' => 'NATSORT(_category.name)' + ]) + ->add('footprint', EntityColumn::class, [ 'property' => 'footprint', 'label' => $this->translator->trans('part.table.footprint'), - ]); - } - if ($this->security->isGranted('@manufacturers.read')) { - $dataTable->add('manufacturer', EntityColumn::class, [ + 'orderField' => 'NATSORT(_footprint.name)' + ]) + ->add('manufacturer', EntityColumn::class, [ 'property' => 'manufacturer', 'label' => $this->translator->trans('part.table.manufacturer'), - ]); - } - if ($this->security->isGranted('@storelocations.read')) { - $dataTable->add('storelocation', TextColumn::class, [ + 'orderField' => 'NATSORT(_manufacturer.name)' + ]) + ->add('storelocation', TextColumn::class, [ 'label' => $this->translator->trans('part.table.storeLocations'), - 'orderField' => 'storelocations.name', - 'render' => function ($value, Part $context): string { - $tmp = []; - foreach ($context->getPartLots() as $lot) { - //Ignore lots without storelocation - if (!$lot->getStorageLocation() instanceof Storelocation) { - continue; - } - $tmp[] = sprintf( - '%s', - $this->urlGenerator->listPartsURL($lot->getStorageLocation()), - htmlspecialchars($lot->getStorageLocation()->getFullPath()), - htmlspecialchars($lot->getStorageLocation()->getName()) - ); - } + //We need to use a aggregate function to get the first store location, as we have a one-to-many relation + 'orderField' => 'NATSORT(MIN(_storelocations.name))', + 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context), + ], alias: 'storage_location') - return implode('
    ', $tmp); - }, - ]); - } - - $dataTable->add('amount', TextColumn::class, [ - 'label' => $this->translator->trans('part.table.amount'), - 'render' => function ($value, Part $context) { - $amount = $context->getAmountSum(); - $expiredAmount = $context->getExpiredAmountSum(); - - $ret = ''; - - if ($context->isAmountUnknown()) { - //When all amounts are unknown, we show a question mark - if ($amount === 0.0) { - $ret .= sprintf('?', - $this->translator->trans('part_lots.instock_unknown')); - } else { //Otherwise mark it with greater equal and the (known) amount - $ret .= sprintf('', - $this->translator->trans('part_lots.instock_unknown') - ); - $ret .= htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit())); - } - } else { - $ret .= htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit())); - } - - //If we have expired lots, we show them in parentheses behind - if ($expiredAmount > 0) { - $ret .= sprintf(' (+%s)', - $this->translator->trans('part_lots.is_expired'), - htmlspecialchars($this->amountFormatter->format($expiredAmount, $context->getPartUnit()))); - } - - //When the amount is below the minimum amount, we highlight the number red - if ($context->isNotEnoughInstock()) { - $ret = sprintf('%s', - $this->translator->trans('part.info.amount.less_than_desired'), - $ret); - } - - return $ret; - }, - 'orderField' => 'amountSum' - ]) + ->add('amount', TextColumn::class, [ + 'label' => $this->translator->trans('part.table.amount'), + 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderAmount($context), + 'orderField' => 'amountSum' + ]) ->add('minamount', TextColumn::class, [ 'label' => $this->translator->trans('part.table.minamount'), - 'visible' => false, - 'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format($value, $context->getPartUnit())), - ]); - - if ($this->security->isGranted('@footprints.read')) { - $dataTable->add('partUnit', TextColumn::class, [ - 'field' => 'partUnit.name', + 'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format( + $value, + $context->getPartUnit() + )), + ]) + ->add('partUnit', TextColumn::class, [ 'label' => $this->translator->trans('part.table.partUnit'), - 'visible' => false, - ]); - } + 'orderField' => 'NATSORT(_partUnit.name)', + 'render' => function ($value, Part $context): string { + $partUnit = $context->getPartUnit(); + if ($partUnit === null) { + return ''; + } - $dataTable->add('addedDate', LocaleDateTimeColumn::class, [ - 'label' => $this->translator->trans('part.table.addedDate'), - 'visible' => false, - ]) + $tmp = htmlspecialchars($partUnit->getName()); + + if ($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'), + ]) ->add('lastModified', LocaleDateTimeColumn::class, [ 'label' => $this->translator->trans('part.table.lastModified'), - 'visible' => false, ]) ->add('needs_review', PrettyBoolColumn::class, [ 'label' => $this->translator->trans('part.table.needsReview'), - 'visible' => false, ]) ->add('favorite', PrettyBoolColumn::class, [ 'label' => $this->translator->trans('part.table.favorite'), - 'visible' => false, ]) ->add('manufacturing_status', EnumColumn::class, [ 'label' => $this->translator->trans('part.table.manufacturingStatus'), - 'visible' => false, 'class' => ManufacturingStatus::class, - 'render' => function(?ManufacturingStatus $status, Part $context): string { - if (!$status) { + 'render' => function (?ManufacturingStatus $status, Part $context): string { + if ($status === null) { return ''; } return $this->translator->trans($status->toTranslationKey()); - } , + }, ]) ->add('manufacturer_product_number', TextColumn::class, [ 'label' => $this->translator->trans('part.table.mpn'), - 'visible' => false, + 'orderField' => 'NATSORT(part.manufacturer_product_number)' ]) ->add('mass', SIUnitNumberColumn::class, [ 'label' => $this->translator->trans('part.table.mass'), - 'visible' => false, 'unit' => 'g' ]) ->add('tags', TagsColumn::class, [ 'label' => $this->translator->trans('part.table.tags'), - 'visible' => false, ]) ->add('attachments', PartAttachmentsColumn::class, [ 'label' => $this->translator->trans('part.table.attachments'), - 'visible' => false, - ]) + ]); + + //Add a column to list the projects where the part is used, when the user has the permission to see the projects + if ($this->security->isGranted('read', Project::class)) { + $this->csh->add('projects', TextColumn::class, [ + 'label' => $this->translator->trans('project.labelp'), + 'render' => function ($value, Part $context): string { + //Only show the first 5 projects names + $projects = $context->getProjects(); + $tmp = ""; + + $max = 5; + + for ($i = 0; $i < min($max, count($projects)); $i++) { + $url = $this->urlGenerator->infoURL($projects[$i]); + $tmp .= sprintf('%s', $url, htmlspecialchars($projects[$i]->getName())); + if ($i < count($projects) - 1) { + $tmp .= ", "; + } + } + + if (count($projects) > $max) { + $tmp .= ", + " . (count($projects) - $max); + } + + return $tmp; + } + ]); + } + + $this->csh ->add('edit', IconLinkColumn::class, [ 'label' => $this->translator->trans('part.table.edit'), - 'visible' => false, 'href' => fn($value, Part $context) => $this->urlGenerator->editURL($context), 'disabled' => fn($value, Part $context) => !$this->security->isGranted('edit', $context), 'title' => $this->translator->trans('part.table.edit.title'), - ]) + ]); - ->addOrderBy('name') - ->createAdapter(TwoStepORMAdapater::class, [ + //Apply the user configured order and visibility and add the columns to the table + $this->csh->applyVisibilityAndConfigureColumns($dataTable, $this->tableSettings->partsDefaultColumns, + "TABLE_PARTS_DEFAULT_COLUMNS"); + + $dataTable->addOrderBy('name') + ->createAdapter(TwoStepORMAdapter::class, [ 'filter_query' => $this->getFilterQuery(...), 'detail_query' => $this->getDetailQuery(...), 'entity' => Part::class, - 'hydrate' => Query::HYDRATE_OBJECT, + 'hydrate' => AbstractQuery::HYDRATE_OBJECT, + //Use the simple total query, as we just want to get the total number of parts without any conditions + //For this the normal query would be pretty slow + 'simple_total_query' => true, 'criteria' => [ function (QueryBuilder $builder) use ($options): void { $this->buildCriteria($builder, $options); }, new SearchCriteriaProvider(), ], + 'query_modifier' => $this->addJoins(...), ]); } @@ -292,42 +290,22 @@ final class PartsDataTable implements DataTableTypeInterface /* In the filter query we only select the IDs. The fetching of the full entities is done in the detail query. * We only need to join the entities here, so we can filter by them. * The filter conditions are added to this QB in the buildCriteria method. + * + * The amountSum field and the joins are dynmically added by the addJoins method, if the fields are used in the query. + * This improves the performance, as we do not need to join all tables, if we do not need them. */ $builder ->select('part.id') ->addSelect('part.minamount AS HIDDEN minamount') - //Calculate amount sum using a subquery, so we can filter and sort by it - ->addSelect( - '( - SELECT IFNULL(SUM(partLot.amount), 0.0) - FROM '. PartLot::class. ' partLot - WHERE partLot.part = part.id - AND partLot.instock_unknown = false - AND (partLot.expiration_date IS NULL OR partLot.expiration_date > CURRENT_DATE()) - ) AS HIDDEN amountSum' - ) ->from(Part::class, 'part') - ->leftJoin('part.category', 'category') - ->leftJoin('part.master_picture_attachment', 'master_picture_attachment') - ->leftJoin('part.partLots', 'partLots') - ->leftJoin('partLots.storage_location', 'storelocations') - ->leftJoin('part.footprint', 'footprint') - ->leftJoin('footprint.master_picture_attachment', 'footprint_attachment') - ->leftJoin('part.manufacturer', 'manufacturer') - ->leftJoin('part.orderdetails', 'orderdetails') - ->leftJoin('orderdetails.supplier', 'suppliers') - ->leftJoin('part.attachments', 'attachments') - ->leftJoin('part.partUnit', 'partUnit') - ->leftJoin('part.parameters', 'parameters') - //This must be the only group by, or the paginator will not work correctly - ->addGroupBy('part.id') - ; + //The other group by fields, are dynamically added by the addJoins method + ->addGroupBy('part'); } private function getDetailQuery(QueryBuilder $builder, array $filter_results): void { - $ids = array_map(fn($row) => $row['id'], $filter_results); + $ids = array_map(static fn($row) => $row['id'], $filter_results); /* * In this query we take the IDs which were filtered, paginated and sorted in the filter query, and fetch the @@ -335,6 +313,8 @@ final class PartsDataTable implements DataTableTypeInterface * We can do complex fetch joins, as we do not need to filter or sort here (which would kill the performance). * The only condition should be for the IDs. * It is important that elements are ordered the same way, as the IDs are passed, or ordering will be wrong. + * + * We do not require the subqueries like amountSum here, as it is not used to render the table (and only for sorting) */ $builder ->select('part') @@ -342,22 +322,13 @@ final class PartsDataTable implements DataTableTypeInterface ->addSelect('footprint') ->addSelect('manufacturer') ->addSelect('partUnit') + ->addSelect('partCustomState') ->addSelect('master_picture_attachment') ->addSelect('footprint_attachment') ->addSelect('partLots') ->addSelect('orderdetails') ->addSelect('attachments') ->addSelect('storelocations') - //Calculate amount sum using a subquery, so we can filter and sort by it - ->addSelect( - '( - SELECT IFNULL(SUM(partLot.amount), 0.0) - FROM '. PartLot::class. ' partLot - WHERE partLot.part = part.id - AND partLot.instock_unknown = false - AND (partLot.expiration_date IS NULL OR partLot.expiration_date > CURRENT_DATE()) - ) AS HIDDEN amountSum' - ) ->from(Part::class, 'part') ->leftJoin('part.category', 'category') ->leftJoin('part.master_picture_attachment', 'master_picture_attachment') @@ -370,8 +341,8 @@ final class PartsDataTable implements DataTableTypeInterface ->leftJoin('orderdetails.supplier', 'suppliers') ->leftJoin('part.attachments', 'attachments') ->leftJoin('part.partUnit', 'partUnit') + ->leftJoin('part.partCustomState', 'partCustomState') ->leftJoin('part.parameters', 'parameters') - ->where('part.id IN (:ids)') ->setParameter('ids', $ids) @@ -388,13 +359,103 @@ final class PartsDataTable implements DataTableTypeInterface ->addGroupBy('suppliers') ->addGroupBy('attachments') ->addGroupBy('partUnit') - ->addGroupBy('parameters') - ; + ->addGroupBy('partCustomState') + ->addGroupBy('parameters'); //Get the results in the same order as the IDs were passed FieldHelper::addOrderByFieldParam($builder, 'part.id', 'ids'); } + /** + * This function is called right before the filter query is executed. + * We use it to dynamically add joins to the query, if the fields are used in the query. + * @param QueryBuilder $builder + * @return QueryBuilder + */ + private function addJoins(QueryBuilder $builder): QueryBuilder + { + //Check if the query contains certain conditions, for which we need to add additional joins + //The join fields get prefixed with an underscore, so we can check if they are used in the query easy without confusing them for a part subfield + $dql = $builder->getDQL(); + + //Add the amountSum field, if it is used in the query + if (str_contains($dql, 'amountSum')) { + //Calculate amount sum using a subquery, so we can filter and sort by it + $builder->addSelect( + '( + SELECT COALESCE(SUM(partLot.amount), 0.0) + FROM ' . PartLot::class . ' partLot + WHERE partLot.part = part.id + AND partLot.instock_unknown = false + AND (partLot.expiration_date IS NULL OR partLot.expiration_date > CURRENT_DATE()) + ) AS HIDDEN amountSum' + ); + } + + if (str_contains($dql, '_category')) { + $builder->leftJoin('part.category', '_category'); + $builder->addGroupBy('_category'); + } + if (str_contains($dql, '_master_picture_attachment')) { + $builder->leftJoin('part.master_picture_attachment', '_master_picture_attachment'); + $builder->addGroupBy('_master_picture_attachment'); + } + if (str_contains($dql, '_partLots') || str_contains($dql, '_storelocations')) { + $builder->leftJoin('part.partLots', '_partLots'); + $builder->leftJoin('_partLots.storage_location', '_storelocations'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_partLots'); + //$builder->addGroupBy('_storelocations'); + } + if (str_contains($dql, '_footprint')) { + $builder->leftJoin('part.footprint', '_footprint'); + $builder->addGroupBy('_footprint'); + } + if (str_contains($dql, '_manufacturer')) { + $builder->leftJoin('part.manufacturer', '_manufacturer'); + $builder->addGroupBy('_manufacturer'); + } + if (str_contains($dql, '_orderdetails') || str_contains($dql, '_suppliers')) { + $builder->leftJoin('part.orderdetails', '_orderdetails'); + $builder->leftJoin('_orderdetails.supplier', '_suppliers'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_orderdetails'); + //$builder->addGroupBy('_suppliers'); + } + if (str_contains($dql, '_attachments')) { + $builder->leftJoin('part.attachments', '_attachments'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_attachments'); + } + if (str_contains($dql, '_partUnit')) { + $builder->leftJoin('part.partUnit', '_partUnit'); + $builder->addGroupBy('_partUnit'); + } + if (str_contains($dql, '_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 + //$builder->addGroupBy('_parameters'); + } + if (str_contains($dql, '_projectBomEntries')) { + $builder->leftJoin('part.project_bom_entries', '_projectBomEntries'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_projectBomEntries'); + } + 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; + } + private function buildCriteria(QueryBuilder $builder, array $options): void { //Apply the search criterias first @@ -408,6 +469,5 @@ final class PartsDataTable implements DataTableTypeInterface $filter = $options['filter']; $filter->apply($builder); } - } } diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index 4586d2f7..fcb06984 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -25,7 +25,6 @@ namespace App\DataTables; use App\DataTables\Column\EntityColumn; use App\DataTables\Column\LocaleDateTimeColumn; use App\DataTables\Column\MarkdownColumn; -use App\DataTables\Column\SelectColumn; use App\DataTables\Helpers\PartDataTableHelper; use App\Entity\Attachments\Attachment; use App\Entity\Parts\Part; @@ -83,24 +82,31 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ->add('name', TextColumn::class, [ 'label' => $this->translator->trans('part.table.name'), - 'orderField' => 'part.name', + 'orderField' => 'NATSORT(part.name)', 'render' => function ($value, ProjectBOMEntry $context) { if(!$context->getPart() instanceof Part) { - return htmlspecialchars($context->getName()); - } - if($context->getPart() instanceof Part) { - $tmp = $this->partDataTableHelper->renderName($context->getPart()); - if($context->getName() !== null && $context->getName() !== '') { - $tmp .= '
    '.htmlspecialchars($context->getName()).''; - } - return $tmp; + return htmlspecialchars((string) $context->getName()); } - //@phpstan-ignore-next-line - throw new \Exception('This should never happen!'); + //Part exists if we reach this point + + $tmp = $this->partDataTableHelper->renderName($context->getPart()); + if($context->getName() !== null && $context->getName() !== '') { + $tmp .= '
    '.htmlspecialchars($context->getName()).''; + } + return $tmp; }, ]) - + ->add('ipn', TextColumn::class, [ + 'label' => $this->translator->trans('part.table.ipn'), + 'orderField' => 'NATSORT(part.ipn)', + 'visible' => false, + 'render' => function ($value, ProjectBOMEntry $context) { + if($context->getPart() instanceof Part) { + return $context->getPart()->getIpn(); + } + } + ]) ->add('description', MarkdownColumn::class, [ 'label' => $this->translator->trans('part.table.description'), 'data' => function (ProjectBOMEntry $context) { @@ -116,18 +122,18 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ->add('category', EntityColumn::class, [ 'label' => $this->translator->trans('part.table.category'), 'property' => 'part.category', - 'orderField' => 'category.name', + 'orderField' => 'NATSORT(category.name)', ]) ->add('footprint', EntityColumn::class, [ 'property' => 'part.footprint', 'label' => $this->translator->trans('part.table.footprint'), - 'orderField' => 'footprint.name', + 'orderField' => 'NATSORT(footprint.name)', ]) ->add('manufacturer', EntityColumn::class, [ 'property' => 'part.manufacturer', 'label' => $this->translator->trans('part.table.manufacturer'), - 'orderField' => 'manufacturer.name', + 'orderField' => 'NATSORT(manufacturer.name)', ]) ->add('mountnames', TextColumn::class, [ @@ -142,6 +148,28 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface }, ]) + ->add('instockAmount', TextColumn::class, [ + 'label' => 'project.bom.instockAmount', + 'visible' => false, + 'render' => function ($value, ProjectBOMEntry $context) { + if ($context->getPart() !== null) { + return $this->partDataTableHelper->renderAmount($context->getPart()); + } + + return ''; + } + ]) + ->add('storageLocations', TextColumn::class, [ + 'label' => 'part.table.storeLocations', + 'visible' => false, + 'render' => function ($value, ProjectBOMEntry $context) { + if ($context->getPart() !== null) { + return $this->partDataTableHelper->renderStorageLocations($context->getPart()); + } + + return ''; + } + ]) ->add('addedDate', LocaleDateTimeColumn::class, [ 'label' => $this->translator->trans('part.table.addedDate'), diff --git a/src/Doctrine/Functions/ArrayPosition.php b/src/Doctrine/Functions/ArrayPosition.php new file mode 100644 index 00000000..39276912 --- /dev/null +++ b/src/Doctrine/Functions/ArrayPosition.php @@ -0,0 +1,59 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Functions; + +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\AST\Node; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\SqlWalker; +use Doctrine\ORM\Query\TokenType; + +class ArrayPosition extends FunctionNode +{ + private ?Node $array = null; + + private ?Node $field = null; + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->array = $parser->InParameter(); + + $parser->match(TokenType::T_COMMA); + + $this->field = $parser->ArithmeticPrimary(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } + + public function getSql(SqlWalker $sqlWalker): string + { + return 'ARRAY_POSITION(' . + $this->array->dispatch($sqlWalker) . ', ' . + $this->field->dispatch($sqlWalker) . + ')'; + } +} \ No newline at end of file diff --git a/src/Doctrine/Functions/Field2.php b/src/Doctrine/Functions/Field2.php index dc12b294..57f55653 100644 --- a/src/Doctrine/Functions/Field2.php +++ b/src/Doctrine/Functions/Field2.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace App\Doctrine\Functions; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\AST\Functions\FunctionNode; -use Doctrine\ORM\Query\Lexer; +use Doctrine\ORM\Query\TokenType; /** * Basically the same as the original Field function, but uses FIELD2 for the SQL query. @@ -36,10 +38,10 @@ class Field2 extends FunctionNode private $values = []; - public function parse(\Doctrine\ORM\Query\Parser $parser) + public function parse(Parser $parser): void { - $parser->match(Lexer::T_IDENTIFIER); - $parser->match(Lexer::T_OPEN_PARENTHESIS); + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); // Do the field. $this->field = $parser->ArithmeticPrimary(); @@ -50,23 +52,24 @@ class Field2 extends FunctionNode $lexer = $parser->getLexer(); while (count($this->values) < 1 || - $lexer->lookahead['type'] != Lexer::T_CLOSE_PARENTHESIS) { - $parser->match(Lexer::T_COMMA); + $lexer->lookahead->type !== TokenType::T_CLOSE_PARENTHESIS) { + $parser->match(TokenType::T_COMMA); $this->values[] = $parser->ArithmeticPrimary(); } - $parser->match(Lexer::T_CLOSE_PARENTHESIS); + $parser->match(TokenType::T_CLOSE_PARENTHESIS); } - public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + public function getSql(SqlWalker $sqlWalker): string { $query = 'FIELD2('; $query .= $this->field->dispatch($sqlWalker); $query .= ', '; + $counter = count($this->values); - for ($i = 0; $i < count($this->values); $i++) { + for ($i = 0; $i < $counter; $i++) { if ($i > 0) { $query .= ', '; } @@ -74,8 +77,6 @@ class Field2 extends FunctionNode $query .= $this->values[$i]->dispatch($sqlWalker); } - $query .= ')'; - - return $query; + return $query . ')'; } } \ No newline at end of file diff --git a/src/Doctrine/Functions/ILike.php b/src/Doctrine/Functions/ILike.php new file mode 100644 index 00000000..ff2d2163 --- /dev/null +++ b/src/Doctrine/Functions/ILike.php @@ -0,0 +1,76 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Functions; + +use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\SqlWalker; +use Doctrine\ORM\Query\TokenType; + +/** + * A platform invariant version of the case-insensitive LIKE operation. + * On MySQL and SQLite this is the normal LIKE, but on PostgreSQL it is the ILIKE operator. + */ +class ILike extends FunctionNode +{ + + public $value = null; + + public $expr = null; + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + $this->value = $parser->StringPrimary(); + $parser->match(TokenType::T_COMMA); + $this->expr = $parser->StringExpression(); + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } + + public function getSql(SqlWalker $sqlWalker): string + { + $platform = $sqlWalker->getConnection()->getDatabasePlatform(); + + if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SQLitePlatform) { + $operator = 'LIKE'; + } elseif ($platform instanceof PostgreSQLPlatform) { + //Use the case-insensitive operator, to have the same behavior as MySQL + $operator = 'ILIKE'; + } else { + throw new \RuntimeException('Platform ' . gettype($platform) . ' does not support case insensitive like expressions.'); + } + + $escape = ""; + if ($platform instanceof SQLitePlatform) { + //SQLite needs ESCAPE explicitly defined backslash as escape character + $escape = " ESCAPE '\\'"; + } + + return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->expr->dispatch($sqlWalker) . $escape . ')'; + } +} diff --git a/src/Doctrine/Functions/Natsort.php b/src/Doctrine/Functions/Natsort.php new file mode 100644 index 00000000..bd05e0d6 --- /dev/null +++ b/src/Doctrine/Functions/Natsort.php @@ -0,0 +1,151 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Functions; + +use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; +use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; +use Doctrine\DBAL\Platforms\MariaDBPlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\AST\Node; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\SqlWalker; +use Doctrine\ORM\Query\TokenType; + +class Natsort extends FunctionNode +{ + private ?Node $field = null; + + private static ?bool $supportsNaturalSort = null; + + private static bool $allowSlowNaturalSort = false; + + /** + * As we can not inject parameters into the function, we use an event listener, to call the value on the static function. + * This is the only way to inject the value into the function. + * @param bool $allow + * @return void + */ + public static function allowSlowNaturalSort(bool $allow = true): void + { + self::$allowSlowNaturalSort = $allow; + } + + /** + * Check if the slow natural sort is allowed + * @return bool + */ + public static function isSlowNaturalSortAllowed(): bool + { + return self::$allowSlowNaturalSort; + } + + /** + * Check if the MariaDB version which is connected to supports the natural sort (meaning it has a version of 10.7.0 or higher) + * The result is cached in memory. + * @param Connection $connection + * @return bool + * @throws Exception + */ + private function mariaDBSupportsNaturalSort(Connection $connection): bool + { + if (self::$supportsNaturalSort !== null) { + return self::$supportsNaturalSort; + } + + $version = $connection->getServerVersion(); + + //Get the effective MariaDB version number + $version = $this->getMariaDbMysqlVersionNumber($version); + + //We need at least MariaDB 10.7.0 to support the natural sort + self::$supportsNaturalSort = version_compare($version, '10.7.0', '>='); + return self::$supportsNaturalSort; + } + + /** + * Taken from Doctrine\DBAL\Driver\AbstractMySQLDriver + * + * Detect MariaDB server version, including hack for some mariadb distributions + * that starts with the prefix '5.5.5-' + * + * @param string $versionString Version string as returned by mariadb server, i.e. '5.5.5-Mariadb-10.0.8-xenial' + */ + private function getMariaDbMysqlVersionNumber(string $versionString) : string + { + if ( ! preg_match( + '/^(?:5\.5\.5-)?(mariadb-)?(?P\d+)\.(?P\d+)\.(?P\d+)/i', + $versionString, + $versionParts + )) { + throw new \RuntimeException('Could not detect MariaDB version from version string ' . $versionString); + } + + return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch']; + } + + public function parse(Parser $parser): void + { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->field = $parser->ArithmeticExpression(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } + + public function getSql(SqlWalker $sqlWalker): string + { + assert($this->field !== null, 'Field is not set'); + + $platform = $sqlWalker->getConnection()->getDatabasePlatform(); + + if ($platform instanceof PostgreSQLPlatform) { + return $this->field->dispatch($sqlWalker) . ' COLLATE numeric'; + } + + if ($platform instanceof MariaDBPlatform && $this->mariaDBSupportsNaturalSort($sqlWalker->getConnection())) { + return 'NATURAL_SORT_KEY(' . $this->field->dispatch($sqlWalker) . ')'; + } + + //Do the following operations only if we allow slow natural sort + if (self::$allowSlowNaturalSort) { + if ($platform instanceof SQLitePlatform) { + return $this->field->dispatch($sqlWalker).' COLLATE NATURAL_CMP'; + } + + if ($platform instanceof AbstractMySQLPlatform) { + return 'NatSortKey(' . $this->field->dispatch($sqlWalker) . ', 0)'; + } + } + + //For every other platform, return the field as is + return $this->field->dispatch($sqlWalker); + } + + +} \ No newline at end of file diff --git a/src/Doctrine/Functions/Regexp.php b/src/Doctrine/Functions/Regexp.php new file mode 100644 index 00000000..d7c6f1e7 --- /dev/null +++ b/src/Doctrine/Functions/Regexp.php @@ -0,0 +1,52 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Functions; + +use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; +use Doctrine\ORM\Query\SqlWalker; + +/** + * Similar to the regexp function, but with support for multi platform. + */ +class Regexp extends \DoctrineExtensions\Query\Mysql\Regexp +{ + public function getSql(SqlWalker $sqlWalker): string + { + $platform = $sqlWalker->getConnection()->getDatabasePlatform(); + + // + if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SQLitePlatform) { + $operator = 'REGEXP'; + } elseif ($platform instanceof PostgreSQLPlatform) { + //Use the case-insensitive operator, to have the same behavior as MySQL + $operator = '~*'; + } else { + throw new \RuntimeException('Platform ' . gettype($platform) . ' does not support regular expressions.'); + } + + return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->regexp->dispatch($sqlWalker) . ')'; + } +} \ No newline at end of file diff --git a/src/Doctrine/Helpers/FieldHelper.php b/src/Doctrine/Helpers/FieldHelper.php index 49dc1475..11300db3 100644 --- a/src/Doctrine/Helpers/FieldHelper.php +++ b/src/Doctrine/Helpers/FieldHelper.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace App\Doctrine\Helpers; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\ORM\QueryBuilder; /** @@ -45,10 +46,10 @@ final class FieldHelper $db_platform = $qb->getEntityManager()->getConnection()->getDatabasePlatform(); //If we are on MySQL, we can just use the FIELD function - if ($db_platform instanceof AbstractMySQLPlatform) { - $param = (is_numeric($bound_param) ? '?' : ":") . (string) $bound_param; + if ($db_platform instanceof AbstractMySQLPlatform ) { + $param = (is_numeric($bound_param) ? '?' : ":").(string)$bound_param; $qb->orderBy("FIELD($field_expr, $param)", $order); - } else { + } else { //Use the sqlite/portable version or postgresql //Retrieve the values from the bound parameter $param = $qb->getParameter($bound_param); if ($param === null) { @@ -57,12 +58,31 @@ final class FieldHelper //Generate a unique key from the field_expr $key = 'field2_' . (string) $bound_param; - self::addSqliteOrderBy($qb, $field_expr, $key, $param->getValue(), $order); + + if ($db_platform instanceof PostgreSQLPlatform) { + self::addPostgresOrderBy($qb, $field_expr, $key, $param->getValue(), $order); + } else { + self::addSqliteOrderBy($qb, $field_expr, $key, $param->getValue(), $order); + } } return $qb; } + + private static function addPostgresOrderBy(QueryBuilder $qb, string $field_expr, string $key, array $values, ?string $order = null): void + { + //Use postgres native array_position function, to get the index of the value in the array + //In the end it gives a similar result as the FIELD function + $qb->orderBy("array_position(:$key, $field_expr)", $order); + + //Convert the values to a literal array, to overcome the problem of passing more than 100 parameters + $values = array_map(fn($value) => is_string($value) ? "'$value'" : $value, $values); + $literalArray = '{' . implode(',', $values) . '}'; + + $qb->setParameter($key, $literalArray); + } + private static function addSqliteOrderBy(QueryBuilder $qb, string $field_expr, string $key, array $values, ?string $order = null): void { //Otherwise we emulate it using @@ -88,11 +108,12 @@ final class FieldHelper //If we are on MySQL, we can just use the FIELD function if ($db_platform instanceof AbstractMySQLPlatform) { - $qb->orderBy("FIELD($field_expr, :field_arr)", $order); + $qb->orderBy("FIELD2($field_expr, :field_arr)", $order); + } elseif ($db_platform instanceof PostgreSQLPlatform) { + //Use the postgres native array_position function + self::addPostgresOrderBy($qb, $field_expr, $key, $values, $order); } else { - //Generate a unique key from the field_expr - - //Otherwise we have to it using the FIELD2 function + //Otherwise use the portable version using string concatenation self::addSqliteOrderBy($qb, $field_expr, $key, $values, $order); } diff --git a/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php b/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php new file mode 100644 index 00000000..2a707e1f --- /dev/null +++ b/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareDriver.php @@ -0,0 +1,51 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Middleware; + +use Composer\CaBundle\CaBundle; +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Driver\Connection; +use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; + +/** + * This middleware sets SSL options for MySQL connections + */ +class MySQLSSLConnectionMiddlewareDriver extends AbstractDriverMiddleware +{ + public function __construct(Driver $wrappedDriver, private readonly bool $enabled, private readonly bool $verify = true) + { + parent::__construct($wrappedDriver); + } + + public function connect(array $params): Connection + { + //Only set this on MySQL connections, as other databases don't support this parameter + if($this->enabled && $params['driver'] === 'pdo_mysql') { + $params['driverOptions'][\PDO::MYSQL_ATTR_SSL_CA] = CaBundle::getSystemCaRootBundlePath(); + $params['driverOptions'][\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $this->verify; + } + + return parent::connect($params); + } +} \ No newline at end of file diff --git a/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareWrapper.php b/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareWrapper.php new file mode 100644 index 00000000..8bd25971 --- /dev/null +++ b/src/Doctrine/Middleware/MySQLSSLConnectionMiddlewareWrapper.php @@ -0,0 +1,39 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Middleware; + +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Driver\Middleware; + +class MySQLSSLConnectionMiddlewareWrapper implements Middleware +{ + public function __construct(private readonly bool $enabled, private readonly bool $verify = true) + { + } + + public function wrap(Driver $driver): Driver + { + return new MySQLSSLConnectionMiddlewareDriver($driver, $this->enabled, $this->verify); + } +} \ No newline at end of file diff --git a/src/Doctrine/SQLiteRegexExtension.php b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php similarity index 74% rename from src/Doctrine/SQLiteRegexExtension.php rename to src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php index 2407880d..ad572d4c 100644 --- a/src/Doctrine/SQLiteRegexExtension.php +++ b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php @@ -1,11 +1,8 @@ . */ -namespace App\Doctrine; + +declare(strict_types=1); + + +namespace App\Doctrine\Middleware; use App\Exceptions\InvalidRegexException; -use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener; -use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface; -use Doctrine\DBAL\Event\ConnectionEventArgs; -use Doctrine\DBAL\Events; -use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Driver\Connection; +use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; /** - * This subscriber is used to add the regexp operator to the SQLite platform. + * This middleware is used to add the regexp operator to the SQLite platform. * As a PHP callback is called for every entry to compare it is most likely much slower than using regex on MySQL. * But as regex is not often used, this should be fine for most use cases, also it is almost impossible to implement a better solution. */ -#[AsDoctrineListener(Events::postConnect)] -class SQLiteRegexExtension +class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware { - public function postConnect(ConnectionEventArgs $eventArgs): void + public function connect(#[\SensitiveParameter] array $params): Connection { - $connection = $eventArgs->getConnection(); + //Do connect process first + $connection = parent::connect($params); // TODO: Change the autogenerated stub - //We only execute this on SQLite databases - if ($connection->getDatabasePlatform() instanceof SqlitePlatform) { + //Then add the functions if we are on SQLite + if ($params['driver'] === 'pdo_sqlite') { $native_connection = $connection->getNativeConnection(); //Ensure that the function really exists on the connection, as it is marked as experimental according to PHP documentation - if($native_connection instanceof \PDO && method_exists($native_connection, 'sqliteCreateFunction' )) { + if($native_connection instanceof \PDO) { $native_connection->sqliteCreateFunction('REGEXP', self::regexp(...), 2, \PDO::SQLITE_DETERMINISTIC); $native_connection->sqliteCreateFunction('FIELD', self::field(...), -1, \PDO::SQLITE_DETERMINISTIC); $native_connection->sqliteCreateFunction('FIELD2', self::field2(...), 2, \PDO::SQLITE_DETERMINISTIC); + + //Create a new collation for natural sorting + $native_connection->sqliteCreateCollation('NATURAL_CMP', strnatcmp(...)); } } + + + return $connection; } /** @@ -60,8 +64,12 @@ class SQLiteRegexExtension * @param string $value * @return int */ - final public static function regexp(string $pattern, string $value): int + final public static function regexp(string $pattern, ?string $value): int { + if ($value === null) { + return 0; + } + try { return (mb_ereg($pattern, $value)) ? 1 : 0; @@ -88,10 +96,9 @@ class SQLiteRegexExtension * This function returns the index (position) of the first argument in the subsequent arguments. * If the first argument is not found or is NULL, 0 is returned. * @param string|int|null $value - * @param mixed ...$array * @return int */ - final public static function field(string|int|null $value, ...$array): int + final public static function field(string|int|null $value, mixed ...$array): int { if ($value === null) { return 0; @@ -107,4 +114,4 @@ class SQLiteRegexExtension return $index + 1; } -} +} \ No newline at end of file diff --git a/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareWrapper.php b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareWrapper.php new file mode 100644 index 00000000..42aafaad --- /dev/null +++ b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareWrapper.php @@ -0,0 +1,35 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Middleware; + +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Driver\Middleware; + +class SQLiteRegexExtensionMiddlewareWrapper implements Middleware +{ + public function wrap(Driver $driver): Driver + { + return new SQLiteRegexExtensionMiddlewareDriver($driver); + } +} \ No newline at end of file diff --git a/src/Doctrine/SetSQLMode/SetSQLModeMiddlewareDriver.php b/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php similarity index 84% rename from src/Doctrine/SetSQLMode/SetSQLModeMiddlewareDriver.php rename to src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php index 5501b231..d05b6b9c 100644 --- a/src/Doctrine/SetSQLMode/SetSQLModeMiddlewareDriver.php +++ b/src/Doctrine/Middleware/SetSQLModeMiddlewareDriver.php @@ -20,11 +20,10 @@ declare(strict_types=1); * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -namespace App\Doctrine\SetSQLMode; +namespace App\Doctrine\Middleware; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; -use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; /** * This command sets the initial command parameter for MySQL connections, so we can set the SQL mode @@ -35,9 +34,9 @@ class SetSQLModeMiddlewareDriver extends AbstractDriverMiddleware public function connect(array $params): Connection { //Only set this on MySQL connections, as other databases don't support this parameter - if($this->getDatabasePlatform() instanceof AbstractMySQLPlatform) { + if($params['driver'] === 'pdo_mysql') { //1002 is \PDO::MYSQL_ATTR_INIT_COMMAND constant value - $params['driverOptions'][1002] = 'SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))'; + $params['driverOptions'][\PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))'; } return parent::connect($params); diff --git a/src/Doctrine/SetSQLMode/SetSQLModeMiddlewareWrapper.php b/src/Doctrine/Middleware/SetSQLModeMiddlewareWrapper.php similarity index 97% rename from src/Doctrine/SetSQLMode/SetSQLModeMiddlewareWrapper.php rename to src/Doctrine/Middleware/SetSQLModeMiddlewareWrapper.php index 34320fa5..3307bc7f 100644 --- a/src/Doctrine/SetSQLMode/SetSQLModeMiddlewareWrapper.php +++ b/src/Doctrine/Middleware/SetSQLModeMiddlewareWrapper.php @@ -20,7 +20,7 @@ declare(strict_types=1); * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -namespace App\Doctrine\SetSQLMode; +namespace App\Doctrine\Middleware; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Middleware; @@ -30,7 +30,6 @@ use Doctrine\DBAL\Driver\Middleware; */ class SetSQLModeMiddlewareWrapper implements Middleware { - public function wrap(Driver $driver): Driver { return new SetSQLModeMiddlewareDriver($driver); diff --git a/src/Doctrine/Migration/ContainerAwareMigrationFactory.php b/src/Doctrine/Migration/ContainerAwareMigrationFactory.php new file mode 100644 index 00000000..81565c0e --- /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 00000000..bd92116a --- /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/Purger/DoNotUsePurgerFactory.php b/src/Doctrine/Purger/DoNotUsePurgerFactory.php new file mode 100644 index 00000000..6d487573 --- /dev/null +++ b/src/Doctrine/Purger/DoNotUsePurgerFactory.php @@ -0,0 +1,53 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Purger; + +use Doctrine\Bundle\FixturesBundle\Purger\PurgerFactory; +use Doctrine\Common\DataFixtures\Purger\ORMPurgerInterface; +use Doctrine\Common\DataFixtures\Purger\PurgerInterface; +use Doctrine\ORM\EntityManagerInterface; + +class DoNotUsePurgerFactory implements PurgerFactory +{ + + public function createForEntityManager( + ?string $emName, + EntityManagerInterface $em, + array $excluded = [], + bool $purgeWithTruncate = false + ): PurgerInterface { + return new class() implements ORMPurgerInterface { + + public function purge(): void + { + throw new \LogicException('Do not use doctrine:fixtures:load directly. Use partdb:fixtures:load instead!'); + } + + public function setEntityManager(EntityManagerInterface $em): void + { + // TODO: Implement setEntityManager() method. + } + }; + } +} \ No newline at end of file diff --git a/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php b/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php index 755fd553..0fbf6cdb 100644 --- a/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php +++ b/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php @@ -27,13 +27,10 @@ use Doctrine\Common\DataFixtures\Purger\PurgerInterface; use Doctrine\Common\DataFixtures\Sorter\TopologicalSorter; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\Identifier; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Mapping\ClassMetadataInfo; - use function array_reverse; use function assert; use function count; @@ -190,7 +187,6 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface //Reseting autoincrement is only supported on MySQL platforms if ($platform instanceof AbstractMySQLPlatform ) { //|| $platform instanceof SqlitePlatform) { - $connection->beginTransaction(); $connection->executeQuery($this->getResetAutoIncrementSQL($tbl, $platform)); } } @@ -209,6 +205,8 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface return 'ALTER TABLE '.$tableIdentifier->getQuotedName($platform).' AUTO_INCREMENT = 1;'; } + throw new \RuntimeException("Resetting autoincrement is not supported on this platform!"); + //This seems to cause problems somehow /*if ($platform instanceof SqlitePlatform) { return 'DELETE FROM `sqlite_sequence` WHERE name = \''.$tableIdentifier->getQuotedName($platform).'\';'; @@ -280,7 +278,7 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface foreach ($classes as $class) { foreach ($class->associationMappings as $assoc) { - if (! $assoc['isOwningSide'] || $assoc['type'] !== ClassMetadataInfo::MANY_TO_MANY) { + if (! $assoc['isOwningSide'] || $assoc['type'] !== ClassMetadata::MANY_TO_MANY) { continue; } diff --git a/src/Doctrine/Types/TinyIntType.php b/src/Doctrine/Types/TinyIntType.php index 9b2fc7a9..c2daeeca 100644 --- a/src/Doctrine/Types/TinyIntType.php +++ b/src/Doctrine/Types/TinyIntType.php @@ -22,7 +22,9 @@ declare(strict_types=1); */ namespace App\Doctrine\Types; +use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Types\Type; /** @@ -33,7 +35,15 @@ class TinyIntType extends Type public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { - return 'TINYINT'; + //MySQL knows the TINYINT type directly + //We do not use the TINYINT for sqlite, as it will be resolved to a BOOL type and bring problems with migrations + if ($platform instanceof AbstractMySQLPlatform ) { + //Use TINYINT(1) to allow for proper migration diffs + return 'TINYINT(1)'; + } + + //For other platforms, we use the smallest integer type available + return $platform->getSmallIntTypeDeclarationSQL($column); } public function getName(): string @@ -41,6 +51,20 @@ class TinyIntType extends Type return 'tinyint'; } + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : int) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform): ?int + { + return $value === null ? null : (int) $value; + } + public function requiresSQLCommentHint(AbstractPlatform $platform): bool { //We use the comment, so that doctrine migrations can properly detect, that nothing has changed and no migration is needed. diff --git a/src/Doctrine/Types/UTCDateTimeImmutableType.php b/src/Doctrine/Types/UTCDateTimeImmutableType.php new file mode 100644 index 00000000..c0d6659d --- /dev/null +++ b/src/Doctrine/Types/UTCDateTimeImmutableType.php @@ -0,0 +1,97 @@ +. + */ + +declare(strict_types=1); + +namespace App\Doctrine\Types; + +use DateTime; +use DateTimeInterface; +use DateTimeZone; +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\DateTimeImmutableType; +use Doctrine\DBAL\Types\DateTimeType; +use Doctrine\DBAL\Types\Exception\InvalidFormat; + +/** + * This DateTimeImmutableType all dates to UTC, so it can be later used with the timezones. + * Taken (and adapted) from here: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/cookbook/working-with-datetime.html. + */ +class UTCDateTimeImmutableType extends DateTimeImmutableType +{ + private static ?DateTimeZone $utc_timezone = null; + + /** + * {@inheritdoc} + * + * @param T $value + * + * @return (T is null ? null : string) + * + * @template T + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string + { + if (!self::$utc_timezone instanceof \DateTimeZone) { + self::$utc_timezone = new DateTimeZone('UTC'); + } + + if ($value instanceof \DateTimeImmutable) { + $value = $value->setTimezone(self::$utc_timezone); + } + + return parent::convertToDatabaseValue($value, $platform); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform): ?\DateTimeImmutable + { + if (!self::$utc_timezone instanceof \DateTimeZone) { + self::$utc_timezone = new DateTimeZone('UTC'); + } + + if (null === $value || $value instanceof \DateTimeImmutable) { + return $value; + } + + $converted = \DateTimeImmutable::createFromFormat( + $platform->getDateTimeFormatString(), + $value, + self::$utc_timezone + ); + + if (!$converted) { + throw InvalidFormat::new( + $value, + static::class, + $platform->getDateTimeFormatString(), + ); + } + + return $converted; + } +} diff --git a/src/Doctrine/Types/UTCDateTimeType.php b/src/Doctrine/Types/UTCDateTimeType.php index c7140252..a6fda747 100644 --- a/src/Doctrine/Types/UTCDateTimeType.php +++ b/src/Doctrine/Types/UTCDateTimeType.php @@ -28,6 +28,7 @@ use DateTimeZone; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\DateTimeType; +use Doctrine\DBAL\Types\Exception\InvalidFormat; /** * This DateTimeType all dates to UTC, so it can be later used with the timezones. @@ -64,11 +65,9 @@ class UTCDateTimeType extends DateTimeType * * @param T $value * - * @return (T is null ? null : DateTimeInterface) - * * @template T */ - public function convertToPHPValue($value, AbstractPlatform $platform): ?\DateTimeInterface + public function convertToPHPValue($value, AbstractPlatform $platform): ?DateTime { if (!self::$utc_timezone instanceof \DateTimeZone) { self::$utc_timezone = new DateTimeZone('UTC'); @@ -85,7 +84,11 @@ class UTCDateTimeType extends DateTimeType ); if (!$converted) { - throw ConversionException::conversionFailedFormat($value, $this->getName(), $platform->getDateTimeFormatString()); + throw InvalidFormat::new( + $value, + static::class, + $platform->getDateTimeFormatString(), + ); } return $converted; diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index f65ec913..259785cb 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -22,17 +22,35 @@ declare(strict_types=1); namespace App\Entity\Attachments; -use App\Repository\AttachmentRepository; -use App\EntityListeners\AttachmentDeleteListener; -use Doctrine\DBAL\Types\Types; +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\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use App\ApiPlatform\DocumentedAPIProperties\DocumentedAPIProperty; +use App\ApiPlatform\Filter\EntityFilter; +use App\ApiPlatform\Filter\LikeFilter; +use App\ApiPlatform\HandleAttachmentsUploadsProcessor; 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 Symfony\Component\Serializer\Annotation\Groups; -use Symfony\Component\Validator\Constraints as Assert; -use function in_array; 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; /** * Class Attachment. @@ -42,15 +60,59 @@ use LogicException; #[ORM\Entity(repositoryClass: AttachmentRepository::class)] #[ORM\InheritanceType('SINGLE_TABLE')] #[ORM\DiscriminatorColumn(name: 'class_name', type: 'string')] -#[ORM\DiscriminatorMap(['PartDB\Part' => 'PartAttachment', 'Part' => 'PartAttachment', 'PartDB\Device' => 'ProjectAttachment', 'Device' => 'ProjectAttachment', 'AttachmentType' => 'AttachmentTypeAttachment', 'Category' => 'CategoryAttachment', 'Footprint' => 'FootprintAttachment', 'Manufacturer' => 'ManufacturerAttachment', 'Currency' => 'CurrencyAttachment', 'Group' => 'GroupAttachment', 'MeasurementUnit' => 'MeasurementUnitAttachment', 'Storelocation' => 'StorelocationAttachment', 'Supplier' => 'SupplierAttachment', 'User' => 'UserAttachment', 'LabelProfile' => 'LabelAttachment'])] +#[ORM\DiscriminatorMap(self::ORM_DISCRIMINATOR_MAP)] #[ORM\EntityListeners([AttachmentDeleteListener::class])] #[ORM\Table(name: '`attachments`')] -#[ORM\Index(name: 'attachments_idx_id_element_id_class_name', columns: ['id', 'element_id', 'class_name'])] -#[ORM\Index(name: 'attachments_idx_class_name_id', columns: ['class_name', 'id'])] -#[ORM\Index(name: 'attachment_name_idx', columns: ['name'])] -#[ORM\Index(name: 'attachment_element_idx', columns: ['class_name', 'element_id'])] +#[ORM\Index(columns: ['id', 'element_id', 'class_name'], name: 'attachments_idx_id_element_id_class_name')] +#[ORM\Index(columns: ['class_name', 'id'], name: 'attachments_idx_class_name_id')] +#[ORM\Index(columns: ['name'], name: 'attachment_name_idx')] +#[ORM\Index(columns: ['class_name', 'element_id'], name: 'attachment_element_idx')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@attachments.list_attachments")'), + new Post(securityPostDenormalize: 'is_granted("create", object)', ), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['attachment:read', 'attachment:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['attachment:write', 'attachment:write:standalone', 'api:basic:write'], 'openapi_definition_name' => 'Write'], + processor: HandleAttachmentsUploadsProcessor::class, +)] +//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)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] +//This discriminator map is required for API platform to know which class to use for deserialization, when creating a new attachment. +#[DiscriminatorMap(typeProperty: '_type', mapping: self::API_DISCRIMINATOR_MAP)] abstract class Attachment extends AbstractNamedDBElement { + private const ORM_DISCRIMINATOR_MAP = ['Part' => PartAttachment::class, 'PartCustomState' => PartCustomStateAttachment::class, 'Device' => ProjectAttachment::class, + 'AttachmentType' => AttachmentTypeAttachment::class, + 'Category' => CategoryAttachment::class, 'Footprint' => FootprintAttachment::class, 'Manufacturer' => ManufacturerAttachment::class, + 'Currency' => CurrencyAttachment::class, 'Group' => GroupAttachment::class, 'MeasurementUnit' => MeasurementUnitAttachment::class, + 'Storelocation' => StorageLocationAttachment::class, 'Supplier' => SupplierAttachment::class, + 'User' => UserAttachment::class, 'LabelProfile' => LabelAttachment::class]; + + /* + * The discriminator map used for API platform. The key should be the same as the api platform short type (the @type JSONLD field). + */ + private const API_DISCRIMINATOR_MAP = ["Part" => PartAttachment::class, "PartCustomState" => PartCustomStateAttachment::class, "Project" => ProjectAttachment::class, + "AttachmentType" => AttachmentTypeAttachment::class, + "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]; + /** * A list of file extensions, that browsers can show directly as image. * Based on: https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types @@ -64,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 @@ -80,40 +138,73 @@ abstract class Attachment extends AbstractNamedDBElement */ protected const ALLOWED_ELEMENT_CLASS = AttachmentContainingDBElement::class; + /** + * @var AttachmentUpload|null The options used for uploading a file to this attachment or modify it. + * This value is not persisted in the database, but is just used to pass options to the upload manager. + * If it is null, no upload process is started. + */ + #[Groups(['attachment:write'])] + protected ?AttachmentUpload $upload = null; + /** * @var string|null the original filename the file had, when the user uploaded it */ #[ORM\Column(type: Types::STRING, nullable: true)] + #[Groups(['attachment:read', 'import'])] + #[Assert\Length(max: 255)] 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(type: Types::STRING, name: 'path')] - 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, length: 2048, nullable: true)] + #[Groups(['attachment:read'])] + #[ApiProperty(example: 'http://example.com/image.jpg')] + #[Assert\Length(max: 2048)] + protected ?string $external_path = null; /** * @var string the name of this element */ #[Assert\NotBlank(message: 'validator.attachment.name_not_blank')] - #[Groups(['simple', 'extended', 'full'])] + #[Groups(['simple', 'extended', 'full', 'attachment:read', 'attachment:write', 'import'])] protected string $name = ''; /** * ORM mapping is done in subclasses (like PartAttachment). * @phpstan-param T|null $element */ + #[Groups(['attachment:read:standalone', 'attachment:write:standalone'])] + #[ApiProperty(writableLink: false)] protected ?AttachmentContainingDBElement $element = null; #[ORM\Column(type: Types::BOOLEAN)] + #[Groups(['attachment:read', 'attachment_write', 'full', 'import'])] protected bool $show_in_table = false; #[Assert\NotNull(message: 'validator.attachment.must_not_be_null')] #[ORM\ManyToOne(targetEntity: AttachmentType::class, inversedBy: 'attachments_with_type')] #[ORM\JoinColumn(name: 'type_id', nullable: false)] - #[Selectable()] + #[Selectable] + #[Groups(['attachment:read', 'attachment:write', 'import', 'full'])] + #[ApiProperty(readableLink: false)] protected ?AttachmentType $attachment_type = null; + #[Groups(['attachment:read'])] + protected ?\DateTimeImmutable $addedDate = null; + #[Groups(['attachment:read'])] + protected ?\DateTimeImmutable $lastModified = null; + + public function __construct() { //parent::__construct(); @@ -130,69 +221,106 @@ abstract class Attachment extends AbstractNamedDBElement } } + /** + * Gets the upload currently associated with this attachment. + * This is only temporary and not persisted directly in the database. + * @internal This function should only be used by the Attachment Submit handler service + * @return AttachmentUpload|null + */ + public function getUpload(): ?AttachmentUpload + { + return $this->upload; + } + + /** + * Sets the current upload for this attachment. + * It will be processed as the attachment is persisted/flushed. + * @param AttachmentUpload|null $upload + * @return $this + */ + public function setUpload(?AttachmentUpload $upload): Attachment + { + $this->upload = $upload; + return $this; + } + + + /*********************************************************** * Various function ***********************************************************/ /** * 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 */ + #[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($this->path, PHP_URL_PATH) ?? '', PATHINFO_EXTENSION); + $extension = pathinfo(parse_url($this->getExternalPath(), PHP_URL_PATH) ?? '', PATHINFO_EXTENSION); //If no extension is found or it is known picture extension, we assume that this is a picture extension - if ($extension === '' || in_array(strtolower($extension), static::PICTURE_EXTS, true)) { - return true; - } - - //Otherwise we assume that the file is not a picture - return false; + 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()) */ - public function isExternal(): bool + #[Groups(['attachment:read'])] + public function hasExternal(): bool { - //When path is empty, this attachment can not be external - if ($this->path === '') { - return false; - } + return $this->external_path !== null && $this->external_path !== ''; + } - //After the %PLACEHOLDER% comes a slash, so we can check if we have a placeholder via explode - $tmp = explode('/', $this->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 !== ''; } /** @@ -201,10 +329,16 @@ abstract class Attachment extends AbstractNamedDBElement * * @return bool true, if the file is secure */ + #[Groups(['attachment:read'])] + #[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]; } @@ -215,9 +349,14 @@ abstract class Attachment extends AbstractNamedDBElement * * @return bool true if the attachment is using a builtin file */ + #[Groups(['attachment:read'])] public function isBuiltIn(): bool { - return static::checkIfBuiltin($this->path); + if ($this->internal_path === null) { + return false; + } + + return static::checkIfBuiltin($this->internal_path); } /******************************************************************************** @@ -229,13 +368,13 @@ abstract class Attachment extends AbstractNamedDBElement /** * 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; } @@ -243,7 +382,7 @@ abstract class Attachment extends AbstractNamedDBElement return strtolower(pathinfo($this->original_filename, PATHINFO_EXTENSION)); } - return strtolower(pathinfo($this->getPath(), PATHINFO_EXTENSION)); + return strtolower(pathinfo($this->getInternalPath(), PATHINFO_EXTENSION)); } /** @@ -258,50 +397,54 @@ abstract class Attachment extends AbstractNamedDBElement } /** - * 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. */ 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($this->getURL(), PHP_URL_HOST); + return parse_url($this->getExternalPath(), 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; } @@ -310,7 +453,7 @@ abstract class Attachment extends AbstractNamedDBElement return $this->original_filename; } - return pathinfo($this->getPath(), PATHINFO_BASENAME); + return pathinfo($this->getInternalPath(), PATHINFO_BASENAME); } /** @@ -372,7 +515,8 @@ abstract class Attachment extends AbstractNamedDBElement */ public function setElement(AttachmentContainingDBElement $element): self { - if (!is_a($element, static::ALLOWED_ELEMENT_CLASS)) { + //Do not allow Rector to replace this check with a instanceof. It will not work!! + if (!is_a($element, static::ALLOWED_ELEMENT_CLASS, true)) { throw new InvalidArgumentException(sprintf('The element associated with a %s must be a %s!', static::class, static::ALLOWED_ELEMENT_CLASS)); } @@ -382,15 +526,12 @@ abstract class Attachment extends AbstractNamedDBElement } /** - * 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; } @@ -406,24 +547,61 @@ abstract class Attachment extends AbstractNamedDBElement } /** - * 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 { - //Only set if the URL is not empty - if ($url !== null && $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; + //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; } + //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; + } + + + /** + * 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; } @@ -434,12 +612,17 @@ abstract class Attachment extends AbstractNamedDBElement /** * 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/AttachmentContainingDBElement.php b/src/Entity/Attachments/AttachmentContainingDBElement.php index f2dfd3ef..a78cb1f4 100644 --- a/src/Entity/Attachments/AttachmentContainingDBElement.php +++ b/src/Entity/Attachments/AttachmentContainingDBElement.php @@ -33,7 +33,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; /** - * @template-covariant AT of Attachment + * @template AT of Attachment */ #[ORM\MappedSuperclass(repositoryClass: AttachmentContainingDBElementRepository::class)] abstract class AttachmentContainingDBElement extends AbstractNamedDBElement implements HasMasterAttachmentInterface, HasAttachmentsInterface @@ -45,7 +45,7 @@ abstract class AttachmentContainingDBElement extends AbstractNamedDBElement impl * @phpstan-var Collection * ORM Mapping is done in subclasses (e.g. Part) */ - #[Groups(['full'])] + #[Groups(['full', 'import'])] protected Collection $attachments; public function __construct() diff --git a/src/Entity/Attachments/AttachmentType.php b/src/Entity/Attachments/AttachmentType.php index dc8de0a0..b5d1ff8c 100644 --- a/src/Entity/Attachments/AttachmentType.php +++ b/src/Entity/Attachments/AttachmentType.php @@ -22,6 +22,22 @@ declare(strict_types=1); namespace App\Entity\Attachments; +use Doctrine\Common\Collections\Criteria; +use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Repository\StructuralDBElementRepository; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractStructuralDBElement; @@ -30,6 +46,9 @@ use App\Validator\Constraints\ValidFileFilter; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\TypeInfo\Type\NullableType; +use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\Validator\Constraints as Assert; /** @@ -39,47 +58,90 @@ use Symfony\Component\Validator\Constraints as Assert; */ #[ORM\Entity(repositoryClass: StructuralDBElementRepository::class)] #[ORM\Table(name: '`attachment_types`')] -#[ORM\Index(name: 'attachment_types_idx_name', columns: ['name'])] -#[ORM\Index(name: 'attachment_types_idx_parent_name', columns: ['parent_id', 'name'])] +#[ORM\Index(columns: ['name'], name: 'attachment_types_idx_name')] +#[ORM\Index(columns: ['parent_id', 'name'], name: 'attachment_types_idx_parent_name')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@attachment_types.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['attachment_type:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['attachment_type:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/attachment_types/{id}/children.{_format}', + operations: [ + new GetCollection(openapi: new Operation(summary: 'Retrieves the children elements of an attachment type.'), + security: 'is_granted("@attachment_types.read")') + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: AttachmentType::class) + ], + normalizationContext: ['groups' => ['attachment_type:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class AttachmentType extends AbstractStructuralDBElement { - #[ORM\OneToMany(targetEntity: AttachmentType::class, mappedBy: 'parent', cascade: ['persist'])] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'parent', targetEntity: AttachmentType::class, cascade: ['persist'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: AttachmentType::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['attachment_type:read', 'attachment_type:write'])] + #[ApiProperty(readableLink: true, writableLink: false, nativeType: new NullableType(new ObjectType(self::class)))] protected ?AbstractStructuralDBElement $parent = null; + /** + * @var string A comma separated list of file types, which are allowed for attachment files. + * Must be in the format of
    accept attribute + * (See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers). + */ #[ORM\Column(type: Types::TEXT)] #[ValidFileFilter] + #[Groups(['attachment_type:read', 'attachment_type:write', 'import', 'extended'])] protected string $filetype_filter = ''; /** * @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: AttachmentTypeAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: AttachmentTypeAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => Criteria::ASC])] + #[Groups(['attachment_type:read', 'attachment_type:write', 'import', 'full'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: AttachmentTypeAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['attachment_type:read', 'attachment_type:write', 'full'])] protected ?Attachment $master_picture_attachment = null; /** @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: AttachmentTypeParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: AttachmentTypeParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] + #[Groups(['attachment_type:read', 'attachment_type:write', 'import', 'full'])] protected Collection $parameters; /** * @var Collection */ - #[ORM\OneToMany(targetEntity: Attachment::class, mappedBy: 'attachment_type')] + #[ORM\OneToMany(mappedBy: 'attachment_type', targetEntity: Attachment::class)] protected Collection $attachments_with_type; + #[Groups(['attachment_type:read'])] + protected ?\DateTimeImmutable $addedDate = null; + #[Groups(['attachment_type:read'])] + protected ?\DateTimeImmutable $lastModified = null; + + public function __construct() { $this->children = new ArrayCollection(); diff --git a/src/Entity/Attachments/AttachmentTypeAttachment.php b/src/Entity/Attachments/AttachmentTypeAttachment.php index 31398595..1e677ff0 100644 --- a/src/Entity/Attachments/AttachmentTypeAttachment.php +++ b/src/Entity/Attachments/AttachmentTypeAttachment.php @@ -22,8 +22,10 @@ declare(strict_types=1); namespace App\Entity\Attachments; +use App\Serializer\APIPlatform\OverrideClassDenormalizer; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Serializer\Attribute\Context; /** * A attachment attached to an attachmentType element. @@ -39,5 +41,6 @@ class AttachmentTypeAttachment extends Attachment */ #[ORM\ManyToOne(targetEntity: AttachmentType::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/Attachments/AttachmentUpload.php b/src/Entity/Attachments/AttachmentUpload.php new file mode 100644 index 00000000..f2b042b7 --- /dev/null +++ b/src/Entity/Attachments/AttachmentUpload.php @@ -0,0 +1,77 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity\Attachments; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\Serializer\Attribute\Groups; + +/** + * This is a DTO representing a file upload for an attachment and which is used to pass data to the Attachment + * submit handler service. + */ +class AttachmentUpload +{ + public function __construct( + /** @var UploadedFile|null The file which was uploaded, or null if the file should not be changed */ + public readonly ?UploadedFile $file, + /** @var string|null The base64 encoded data of the file which should be uploaded. */ + #[Groups(['attachment:write'])] + public readonly ?string $data = null, + /** @vaar string|null The original filename of the file passed in data. */ + #[Groups(['attachment:write'])] + public readonly ?string $filename = null, + /** @var bool True, if the URL in the attachment should be downloaded by Part-DB */ + #[Groups(['attachment:write'])] + public readonly bool $downloadUrl = false, + /** @var bool If true the file will be moved to private attachment storage, + * if false it will be moved to public attachment storage. On null file is not moved + */ + #[Groups(['attachment:write'])] + public readonly ?bool $private = null, + /** @var bool If true and no preview image was set yet, the new uploaded file will become the preview image */ + #[Groups(['attachment:write'])] + public readonly ?bool $becomePreviewIfEmpty = true, + ) { + } + + /** + * Creates an AttachmentUpload object from an Attachment FormInterface + * @param FormInterface $form + * @return AttachmentUpload + */ + public static function fromAttachmentForm(FormInterface $form): AttachmentUpload + { + if (!$form->has('file')) { + throw new \InvalidArgumentException('The form does not have a file field. Is it an attachment form?'); + } + + return new self( + file: $form->get('file')->getData(), + downloadUrl: $form->get('downloadURL')->getData(), + private: $form->get('secureFile')->getData() + ); + + } +} \ No newline at end of file diff --git a/src/Entity/Attachments/CategoryAttachment.php b/src/Entity/Attachments/CategoryAttachment.php index 63b4178d..3bea265e 100644 --- a/src/Entity/Attachments/CategoryAttachment.php +++ b/src/Entity/Attachments/CategoryAttachment.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace App\Entity\Attachments; use App\Entity\Parts\Category; +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 category element. @@ -40,5 +42,6 @@ class CategoryAttachment extends Attachment */ #[ORM\ManyToOne(targetEntity: Category::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/Attachments/CurrencyAttachment.php b/src/Entity/Attachments/CurrencyAttachment.php index 292da013..a5d6e061 100644 --- a/src/Entity/Attachments/CurrencyAttachment.php +++ b/src/Entity/Attachments/CurrencyAttachment.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace App\Entity\Attachments; use App\Entity\PriceInformations\Currency; +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 currency element. @@ -41,5 +43,6 @@ class CurrencyAttachment extends Attachment */ #[ORM\ManyToOne(targetEntity: Currency::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/Attachments/FootprintAttachment.php b/src/Entity/Attachments/FootprintAttachment.php index 4d9b7caa..4a9b866c 100644 --- a/src/Entity/Attachments/FootprintAttachment.php +++ b/src/Entity/Attachments/FootprintAttachment.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace App\Entity\Attachments; use App\Entity\Parts\Footprint; +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 footprint element. @@ -41,5 +43,6 @@ class FootprintAttachment extends Attachment */ #[ORM\ManyToOne(targetEntity: Footprint::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/Attachments/GroupAttachment.php b/src/Entity/Attachments/GroupAttachment.php index 269e5b7d..e9bf947f 100644 --- a/src/Entity/Attachments/GroupAttachment.php +++ b/src/Entity/Attachments/GroupAttachment.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace App\Entity\Attachments; use App\Entity\UserSystem\Group; +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 Group element. @@ -41,5 +43,6 @@ class GroupAttachment extends Attachment */ #[ORM\ManyToOne(targetEntity: Group::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/Attachments/LabelAttachment.php b/src/Entity/Attachments/LabelAttachment.php index 5bf18969..b8891ced 100644 --- a/src/Entity/Attachments/LabelAttachment.php +++ b/src/Entity/Attachments/LabelAttachment.php @@ -42,8 +42,10 @@ declare(strict_types=1); namespace App\Entity\Attachments; use App\Entity\LabelSystem\LabelProfile; +use App\Serializer\APIPlatform\OverrideClassDenormalizer; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Serializer\Attribute\Context; /** * A attachment attached to a user element. @@ -60,5 +62,6 @@ class LabelAttachment extends Attachment */ #[ORM\ManyToOne(targetEntity: LabelProfile::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/Attachments/ManufacturerAttachment.php b/src/Entity/Attachments/ManufacturerAttachment.php index cdbd7807..1b8891e5 100644 --- a/src/Entity/Attachments/ManufacturerAttachment.php +++ b/src/Entity/Attachments/ManufacturerAttachment.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace App\Entity\Attachments; use App\Entity\Parts\Manufacturer; +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 manufacturer element. @@ -41,5 +43,6 @@ class ManufacturerAttachment extends Attachment */ #[ORM\ManyToOne(targetEntity: Manufacturer::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/Attachments/MeasurementUnitAttachment.php b/src/Entity/Attachments/MeasurementUnitAttachment.php index 58467f86..dbc8829e 100644 --- a/src/Entity/Attachments/MeasurementUnitAttachment.php +++ b/src/Entity/Attachments/MeasurementUnitAttachment.php @@ -22,10 +22,11 @@ declare(strict_types=1); namespace App\Entity\Attachments; -use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; +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 measurement unit element. @@ -36,10 +37,10 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; class MeasurementUnitAttachment extends Attachment { final public const ALLOWED_ELEMENT_CLASS = MeasurementUnit::class; - /** - * @var Manufacturer|null the element this attachment is associated with - */ + + #[ORM\ManyToOne(targetEntity: MeasurementUnit::class, inversedBy: 'attachments')] #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] + #[Context(denormalizationContext: [OverrideClassDenormalizer::CONTEXT_KEY => self::ALLOWED_ELEMENT_CLASS])] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Attachments/PartAttachment.php b/src/Entity/Attachments/PartAttachment.php index f9ca43b3..0873b3c5 100644 --- a/src/Entity/Attachments/PartAttachment.php +++ b/src/Entity/Attachments/PartAttachment.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace App\Entity\Attachments; use App\Entity\Parts\Part; +use App\Serializer\APIPlatform\OverrideClassDenormalizer; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Serializer\Attribute\Context; /** * A attachment attached to a part element. @@ -40,5 +42,6 @@ class PartAttachment extends Attachment */ #[ORM\ManyToOne(targetEntity: Part::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/Attachments/PartCustomStateAttachment.php b/src/Entity/Attachments/PartCustomStateAttachment.php new file mode 100644 index 00000000..3a561b13 --- /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/Attachments/ProjectAttachment.php b/src/Entity/Attachments/ProjectAttachment.php index 84b0ac4c..f3e96292 100644 --- a/src/Entity/Attachments/ProjectAttachment.php +++ b/src/Entity/Attachments/ProjectAttachment.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace App\Entity\Attachments; use App\Entity\ProjectSystem\Project; +use App\Serializer\APIPlatform\OverrideClassDenormalizer; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Serializer\Attribute\Context; /** * A attachment attached to a device element. @@ -40,5 +42,6 @@ class ProjectAttachment extends Attachment */ #[ORM\ManyToOne(targetEntity: Project::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/Attachments/StorelocationAttachment.php b/src/Entity/Attachments/StorageLocationAttachment.php similarity index 68% rename from src/Entity/Attachments/StorelocationAttachment.php rename to src/Entity/Attachments/StorageLocationAttachment.php index 7772269d..3cd82d0c 100644 --- a/src/Entity/Attachments/StorelocationAttachment.php +++ b/src/Entity/Attachments/StorageLocationAttachment.php @@ -22,24 +22,27 @@ declare(strict_types=1); namespace App\Entity\Attachments; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; +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 measurement unit element. - * @extends Attachment + * @extends Attachment */ #[UniqueEntity(['name', 'attachment_type', 'element'])] #[ORM\Entity] -class StorelocationAttachment extends Attachment +class StorageLocationAttachment extends Attachment { - final public const ALLOWED_ELEMENT_CLASS = Storelocation::class; + final public const ALLOWED_ELEMENT_CLASS = StorageLocation::class; /** - * @var Storelocation|null the element this attachment is associated with + * @var StorageLocation|null the element this attachment is associated with */ - #[ORM\ManyToOne(targetEntity: Storelocation::class, inversedBy: 'attachments')] + #[ORM\ManyToOne(targetEntity: StorageLocation::class, inversedBy: 'attachments')] #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] + #[Context(denormalizationContext: [OverrideClassDenormalizer::CONTEXT_KEY => self::ALLOWED_ELEMENT_CLASS])] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Attachments/SupplierAttachment.php b/src/Entity/Attachments/SupplierAttachment.php index 7afa8282..c3adc438 100644 --- a/src/Entity/Attachments/SupplierAttachment.php +++ b/src/Entity/Attachments/SupplierAttachment.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace App\Entity\Attachments; use App\Entity\Parts\Supplier; +use App\Serializer\APIPlatform\OverrideClassDenormalizer; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Serializer\Attribute\Context; /** * A attachment attached to a supplier element. @@ -41,5 +43,6 @@ class SupplierAttachment extends Attachment */ #[ORM\ManyToOne(targetEntity: Supplier::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/Attachments/UserAttachment.php b/src/Entity/Attachments/UserAttachment.php index fef36eb5..b031d419 100644 --- a/src/Entity/Attachments/UserAttachment.php +++ b/src/Entity/Attachments/UserAttachment.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace App\Entity\Attachments; use App\Entity\UserSystem\User; +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 user element. @@ -41,5 +43,6 @@ class UserAttachment extends Attachment */ #[ORM\ManyToOne(targetEntity: User::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 ca6d1b88..7d05c93f 100644 --- a/src/Entity/Base/AbstractCompany.php +++ b/src/Entity/Base/AbstractCompany.php @@ -33,54 +33,69 @@ use Symfony\Component\Validator\Constraints as Assert; /** * This abstract class is used for companies like suppliers or manufacturers. * - * @template-covariant AT of Attachment - * @template-covariant PT of AbstractParameter + * @template AT of Attachment + * @template PT of AbstractParameter * @extends AbstractPartsContainingDBElement */ #[ORM\MappedSuperclass] abstract class AbstractCompany extends AbstractPartsContainingDBElement { + #[Groups(['company:read'])] + protected ?\DateTimeImmutable $addedDate = null; + #[Groups(['company:read'])] + protected ?\DateTimeImmutable $lastModified = null; + /** * @var string The address of the company */ - #[Groups(['full'])] + #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] #[ORM\Column(type: Types::STRING)] + #[Assert\Length(max: 255)] protected string $address = ''; /** * @var string The phone number of the company */ - #[Groups(['full'])] + #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] #[ORM\Column(type: Types::STRING)] + #[Assert\Length(max: 255)] protected string $phone_number = ''; /** * @var string The fax number of the company */ - #[Groups(['full'])] + #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] #[ORM\Column(type: Types::STRING)] + #[Assert\Length(max: 255)] protected string $fax_number = ''; /** * @var string The email address of the company */ #[Assert\Email] - #[Groups(['full'])] + #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] #[ORM\Column(type: Types::STRING)] + #[Assert\Length(max: 255)] protected string $email_address = ''; /** * @var string The website of the company */ - #[Assert\Url] - #[Groups(['full'])] - #[ORM\Column(type: Types::STRING)] + #[Assert\Url(requireTld: false)] + #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] + #[ORM\Column(type: Types::STRING, length: 2048)] + #[Assert\Length(max: 2048)] protected string $website = ''; + #[Groups(['company:read', 'company:write', 'import', 'full', 'extended'])] + protected string $comment = ''; + /** - * @var string + * @var string The link to the website of an article. Use %PARTNUMBER% as placeholder for the part number. */ - #[ORM\Column(type: Types::STRING)] + #[ORM\Column(type: Types::STRING, length: 2048)] + #[Assert\Length(max: 2048)] + #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] protected string $auto_product_url = ''; /******************************************************************************** @@ -147,7 +162,7 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement * * @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 30fcab06..a088b3df 100644 --- a/src/Entity/Base/AbstractDBElement.php +++ b/src/Entity/Base/AbstractDBElement.php @@ -33,11 +33,15 @@ use App\Entity\Attachments\LabelAttachment; use App\Entity\Attachments\ManufacturerAttachment; use App\Entity\Attachments\MeasurementUnitAttachment; use App\Entity\Attachments\PartAttachment; +use App\Entity\Attachments\PartCustomStateAttachment; use App\Entity\Attachments\ProjectAttachment; -use App\Entity\Attachments\StorelocationAttachment; +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; @@ -45,7 +49,7 @@ use App\Entity\UserSystem\Group; use App\Entity\Parts\Manufacturer; use App\Entity\PriceInformations\Orderdetail; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\PartLot; use App\Entity\PriceInformations\Currency; use App\Entity\Parts\MeasurementUnit; @@ -66,14 +70,48 @@ use Symfony\Component\Serializer\Annotation\Groups; * Every database table which are managed with this class (or a subclass of it) * must have the table row "id"!! The ID is the unique key to identify the elements. */ -#[DiscriminatorMap(typeProperty: 'type', mapping: ['attachment_type' => AttachmentType::class, 'attachment' => Attachment::class, 'attachment_type_attachment' => AttachmentTypeAttachment::class, 'category_attachment' => CategoryAttachment::class, 'currency_attachment' => CurrencyAttachment::class, 'footprint_attachment' => FootprintAttachment::class, 'group_attachment' => GroupAttachment::class, 'label_attachment' => LabelAttachment::class, 'manufacturer_attachment' => ManufacturerAttachment::class, 'measurement_unit_attachment' => MeasurementUnitAttachment::class, 'part_attachment' => PartAttachment::class, 'project_attachment' => ProjectAttachment::class, 'storelocation_attachment' => StorelocationAttachment::class, 'supplier_attachment' => SupplierAttachment::class, 'user_attachment' => UserAttachment::class, 'category' => Category::class, 'project' => Project::class, 'project_bom_entry' => ProjectBOMEntry::class, 'footprint' => Footprint::class, 'group' => Group::class, 'manufacturer' => Manufacturer::class, 'orderdetail' => Orderdetail::class, 'part' => Part::class, 'pricedetail' => 'App\Entity\PriceInformation\Pricedetail', 'storelocation' => Storelocation::class, 'part_lot' => PartLot::class, 'currency' => Currency::class, 'measurement_unit' => MeasurementUnit::class, 'parameter' => 'App\Entity\Parts\AbstractParameter', 'supplier' => Supplier::class, 'user' => User::class])] +#[DiscriminatorMap(typeProperty: 'type', mapping: [ + 'attachment_type' => AttachmentType::class, + 'attachment' => Attachment::class, + 'attachment_type_attachment' => AttachmentTypeAttachment::class, + 'category_attachment' => CategoryAttachment::class, + 'currency_attachment' => CurrencyAttachment::class, + 'footprint_attachment' => FootprintAttachment::class, + 'group_attachment' => GroupAttachment::class, + 'label_attachment' => LabelAttachment::class, + 'manufacturer_attachment' => ManufacturerAttachment::class, + 'measurement_unit_attachment' => MeasurementUnitAttachment::class, + 'part_attachment' => PartAttachment::class, + '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 { /** @var int|null The Identification number for this part. This value is unique for the element in this table. * Null if the element is not saved to DB yet. */ - #[Groups(['full'])] + #[Groups(['full', 'api:basic:read'])] #[ORM\Column(type: Types::INTEGER)] #[ORM\Id] #[ORM\GeneratedValue] diff --git a/src/Entity/Base/AbstractNamedDBElement.php b/src/Entity/Base/AbstractNamedDBElement.php index e5e30441..f7939589 100644 --- a/src/Entity/Base/AbstractNamedDBElement.php +++ b/src/Entity/Base/AbstractNamedDBElement.php @@ -40,11 +40,12 @@ abstract class AbstractNamedDBElement extends AbstractDBElement implements Named use TimestampTrait; /** - * @var string the name of this element + * @var string The name of this element */ #[Assert\NotBlank] - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'api:basic:read', 'api:basic:write'])] #[ORM\Column(type: Types::STRING)] + #[Assert\Length(max: 255)] protected string $name = ''; /****************************************************************************** diff --git a/src/Entity/Base/AbstractPartsContainingDBElement.php b/src/Entity/Base/AbstractPartsContainingDBElement.php index 5d25283e..70d88fa9 100644 --- a/src/Entity/Base/AbstractPartsContainingDBElement.php +++ b/src/Entity/Base/AbstractPartsContainingDBElement.php @@ -23,9 +23,7 @@ declare(strict_types=1); namespace App\Entity\Base; use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Parameters\AbstractParameter; -use App\Entity\Parameters\ParametersTrait; use App\Repository\AbstractPartsContainingRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -33,14 +31,14 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; /** - * @template-covariant AT of Attachment - * @template-covariant PT of AbstractParameter + * @template AT of Attachment + * @template PT of AbstractParameter * @extends AbstractStructuralDBElement */ #[ORM\MappedSuperclass(repositoryClass: AbstractPartsContainingRepository::class)] abstract class AbstractPartsContainingDBElement extends AbstractStructuralDBElement { - #[Groups(['full'])] + #[Groups(['full', 'import'])] protected Collection $parameters; public function __construct() diff --git a/src/Entity/Base/AbstractStructuralDBElement.php b/src/Entity/Base/AbstractStructuralDBElement.php index 5c4103d8..660710db 100644 --- a/src/Entity/Base/AbstractStructuralDBElement.php +++ b/src/Entity/Base/AbstractStructuralDBElement.php @@ -26,18 +26,17 @@ use App\Entity\Attachments\Attachment; use App\Entity\Parameters\AbstractParameter; use App\Repository\StructuralDBElementRepository; use App\EntityListeners\TreeCacheInvalidationListener; -use Doctrine\Common\Proxy\Proxy; +use App\Validator\Constraints\UniqueObjectCollection; use Doctrine\DBAL\Types\Types; use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Parameters\ParametersTrait; use App\Validator\Constraints\NoneOfItsChildren; +use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Validator\Constraints as Assert; -use Symfony\Component\Validator\Constraints\Valid; use function count; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use function get_class; use InvalidArgumentException; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation\Groups; @@ -54,13 +53,13 @@ use Symfony\Component\Serializer\Annotation\Groups; * * @see \App\Tests\Entity\Base\AbstractStructuralDBElementTest * - * @template-covariant AT of Attachment - * @template-covariant PT of AbstractParameter + * @template AT of Attachment + * @template PT of AbstractParameter * @template-use ParametersTrait * @extends AttachmentContainingDBElement * @uses ParametersTrait */ -#[UniqueEntity(fields: ['name', 'parent'], ignoreNull: false, message: 'structural.entity.unique_name')] +#[UniqueEntity(fields: ['name', 'parent'], message: 'structural.entity.unique_name', ignoreNull: false)] #[ORM\MappedSuperclass(repositoryClass: StructuralDBElementRepository::class)] #[ORM\EntityListeners([TreeCacheInvalidationListener::class])] abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement @@ -73,7 +72,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement final public const PATH_DELIMITER_ARROW = ' → '; /** - * @var string The comment info for this element + * @var string The comment info for this element as markdown */ #[Groups(['full', 'import'])] #[ORM\Column(type: Types::TEXT)] @@ -116,7 +115,8 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement * @var Collection * @phpstan-var Collection */ - #[Assert\Valid()] + #[Assert\Valid] + #[UniqueObjectCollection(fields: ['name', 'group', 'element'])] protected Collection $parameters; /** @var string[] all names of all parent elements as an array of strings, @@ -174,7 +174,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement throw new InvalidArgumentException('isChildOf() only works for objects of the same type!'); } - if (!$this->getParent() instanceof \App\Entity\Base\AbstractStructuralDBElement) { // this is the root node + if (!$this->getParent() instanceof self) { // this is the root node return false; } @@ -219,7 +219,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement } /** - * Get the comment of the element. + * Get the comment of the element as markdown encoded string. * * @return string the comment @@ -242,9 +242,9 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement /* * Only check for nodes that have a parent. In the other cases zero is correct. */ - if (0 === $this->level && $this->parent instanceof \App\Entity\Base\AbstractStructuralDBElement) { + if (0 === $this->level && $this->parent instanceof self) { $element = $this->parent; - while ($element instanceof \App\Entity\Base\AbstractStructuralDBElement) { + while ($element instanceof self) { /** @var AbstractStructuralDBElement $element */ $element = $element->parent; ++$this->level; @@ -261,6 +261,8 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement * * @return string the full path (incl. the name of this element), delimited by $delimiter */ + #[Groups(['api:basic:read'])] + #[SerializedName('full_path')] public function getFullPath(string $delimiter = self::PATH_DELIMITER_ARROW): string { if ($this->full_path_strings === []) { @@ -270,7 +272,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement $overflow = 20; //We only allow 20 levels depth - while ($element->parent instanceof \App\Entity\Base\AbstractStructuralDBElement && $overflow >= 0) { + while ($element->parent instanceof self && $overflow >= 0) { $element = $element->parent; $this->full_path_strings[] = $element->getName(); //Decrement to prevent mem overflow. @@ -316,6 +318,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement return new ArrayCollection(); } + //@phpstan-ignore-next-line return $this->children ?? new ArrayCollection(); } @@ -356,7 +359,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement $this->parent = $new_parent; //Add this element as child to the new parent - if ($new_parent instanceof \App\Entity\Base\AbstractStructuralDBElement) { + if ($new_parent instanceof self) { $new_parent->getChildren()->add($this); } @@ -441,7 +444,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement public function setAlternativeNames(?string $new_value): self { //Add a trailing comma, if not already there (makes it easier to find in the database) - if (is_string($new_value) && substr($new_value, -1) !== ',') { + if (is_string($new_value) && !str_ends_with($new_value, ',')) { $new_value .= ','; } diff --git a/src/Entity/Base/MasterAttachmentTrait.php b/src/Entity/Base/MasterAttachmentTrait.php index aec15633..723bab07 100644 --- a/src/Entity/Base/MasterAttachmentTrait.php +++ b/src/Entity/Base/MasterAttachmentTrait.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace App\Entity\Base; use App\Entity\Attachments\Attachment; -use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** diff --git a/src/Entity/Base/PartsContainingRepositoryInterface.php b/src/Entity/Base/PartsContainingRepositoryInterface.php index f852bc35..89e7e5f6 100644 --- a/src/Entity/Base/PartsContainingRepositoryInterface.php +++ b/src/Entity/Base/PartsContainingRepositoryInterface.php @@ -30,11 +30,11 @@ interface PartsContainingRepositoryInterface * Returns all parts associated with this element. * * @param object $element the element for which the parts should be determined - * @param array $order_by The order of the parts. Format ['name' => 'ASC'] + * @param string $nameOrderDirection the direction in which the parts should be ordered by name, either ASC or DESC * * @return Part[] */ - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array; + public function getParts(object $element, string $nameOrderDirection = "ASC"): array; /** * Gets the count of the parts associated with this element. diff --git a/src/Entity/Base/TimestampTrait.php b/src/Entity/Base/TimestampTrait.php index 93e58cb7..77506b18 100644 --- a/src/Entity/Base/TimestampTrait.php +++ b/src/Entity/Base/TimestampTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Base; +use ApiPlatform\Metadata\ApiProperty; use Doctrine\DBAL\Types\Types; use DateTime; use Doctrine\ORM\Mapping as ORM; @@ -33,26 +34,28 @@ use Symfony\Component\Serializer\Annotation\Groups; trait TimestampTrait { /** - * @var \DateTimeInterface|null the date when this element was modified the last time + * @var \DateTimeImmutable|null the date when this element was modified the last time */ #[Groups(['extended', 'full'])] - #[ORM\Column(name: 'last_modified', type: Types::DATETIME_MUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] - protected ?\DateTimeInterface $lastModified = null; + #[ApiProperty(writable: false)] + #[ORM\Column(name: 'last_modified', type: Types::DATETIME_IMMUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] + protected ?\DateTimeImmutable $lastModified = null; /** - * @var \DateTimeInterface|null the date when this element was created + * @var \DateTimeImmutable|null the date when this element was created */ #[Groups(['extended', 'full'])] - #[ORM\Column(name: 'datetime_added', type: Types::DATETIME_MUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] - protected ?\DateTimeInterface $addedDate = null; + #[ApiProperty(writable: false)] + #[ORM\Column(name: 'datetime_added', type: Types::DATETIME_IMMUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] + protected ?\DateTimeImmutable $addedDate = null; /** * Returns the last time when the element was modified. * Returns null if the element was not yet saved to DB yet. * - * @return \DateTimeInterface|null the time of the last edit + * @return \DateTimeImmutable|null the time of the last edit */ - public function getLastModified(): ?\DateTimeInterface + public function getLastModified(): ?\DateTimeImmutable { return $this->lastModified; } @@ -61,9 +64,9 @@ trait TimestampTrait * Returns the date/time when the element was created. * Returns null if the element was not yet saved to DB yet. * - * @return \DateTimeInterface|null the creation time of the part + * @return \DateTimeImmutable|null the creation time of the part */ - public function getAddedDate(): ?\DateTimeInterface + public function getAddedDate(): ?\DateTimeImmutable { return $this->addedDate; } @@ -75,9 +78,9 @@ trait TimestampTrait #[ORM\PreUpdate] public function updateTimestamps(): void { - $this->lastModified = new DateTime('now'); + $this->lastModified = new \DateTimeImmutable('now'); if (null === $this->addedDate) { - $this->addedDate = new DateTime('now'); + $this->addedDate = new \DateTimeImmutable('now'); } } } diff --git a/src/Entity/Contracts/TimeStampableInterface.php b/src/Entity/Contracts/TimeStampableInterface.php index 6393d629..c99c0a1c 100644 --- a/src/Entity/Contracts/TimeStampableInterface.php +++ b/src/Entity/Contracts/TimeStampableInterface.php @@ -22,8 +22,6 @@ declare(strict_types=1); namespace App\Entity\Contracts; -use DateTime; - interface TimeStampableInterface { /** diff --git a/src/Entity/Contracts/TimeTravelInterface.php b/src/Entity/Contracts/TimeTravelInterface.php index 064f4fec..2b0f4571 100644 --- a/src/Entity/Contracts/TimeTravelInterface.php +++ b/src/Entity/Contracts/TimeTravelInterface.php @@ -22,8 +22,6 @@ declare(strict_types=1); namespace App\Entity\Contracts; -use DateTime; - interface TimeTravelInterface { /** diff --git a/src/Entity/EDA/EDACategoryInfo.php b/src/Entity/EDA/EDACategoryInfo.php new file mode 100644 index 00000000..0163dfb3 --- /dev/null +++ b/src/Entity/EDA/EDACategoryInfo.php @@ -0,0 +1,135 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity\EDA; + +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Embeddable; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints\Length; + +#[Embeddable] +class EDACategoryInfo +{ + /** + * @var string|null The reference prefix of the Part in the schematic. E.g. "R" for resistors, or "C" for capacitors. + */ + #[Column(type: Types::STRING, nullable: true)] + #[Groups(['full', 'category:read', 'category:write', 'import'])] + #[Length(max: 255)] + private ?string $reference_prefix = null; + + /** @var bool|null Visibility of this part to EDA software in trinary logic. True=Visible, False=Invisible, Null=Auto */ + #[Column(name: 'invisible', type: Types::BOOLEAN, nullable: true)] //TODO: Rename column to visibility + #[Groups(['full', 'category:read', 'category:write', 'import'])] + private ?bool $visibility = null; + + /** @var bool|null If this is set to true, then this part will be excluded from the BOM */ + #[Column(type: Types::BOOLEAN, nullable: true)] + #[Groups(['full', 'category:read', 'category:write', 'import'])] + private ?bool $exclude_from_bom = null; + + /** @var bool|null If this is set to true, then this part will be excluded from the board/the PCB */ + #[Column(type: Types::BOOLEAN, nullable: true)] + #[Groups(['full', 'category:read', 'category:write', 'import'])] + private ?bool $exclude_from_board = null; + + /** @var bool|null If this is set to true, then this part will be excluded in the simulation */ + #[Column(type: Types::BOOLEAN, nullable: true)] + #[Groups(['full', 'category:read', 'category:write', 'import'])] + private ?bool $exclude_from_sim = true; + + /** @var string|null The KiCAD schematic symbol, which should be used (the path to the library) */ + #[Column(type: Types::STRING, nullable: true)] + #[Groups(['full', 'category:read', 'category:write', 'import'])] + #[Length(max: 255)] + private ?string $kicad_symbol = null; + + public function getReferencePrefix(): ?string + { + return $this->reference_prefix; + } + + public function setReferencePrefix(?string $reference_prefix): EDACategoryInfo + { + $this->reference_prefix = $reference_prefix; + return $this; + } + + public function getVisibility(): ?bool + { + return $this->visibility; + } + + public function setVisibility(?bool $visibility): EDACategoryInfo + { + $this->visibility = $visibility; + return $this; + } + + public function getExcludeFromBom(): ?bool + { + return $this->exclude_from_bom; + } + + public function setExcludeFromBom(?bool $exclude_from_bom): EDACategoryInfo + { + $this->exclude_from_bom = $exclude_from_bom; + return $this; + } + + public function getExcludeFromBoard(): ?bool + { + return $this->exclude_from_board; + } + + public function setExcludeFromBoard(?bool $exclude_from_board): EDACategoryInfo + { + $this->exclude_from_board = $exclude_from_board; + return $this; + } + + public function getExcludeFromSim(): ?bool + { + return $this->exclude_from_sim; + } + + public function setExcludeFromSim(?bool $exclude_from_sim): EDACategoryInfo + { + $this->exclude_from_sim = $exclude_from_sim; + return $this; + } + + public function getKicadSymbol(): ?string + { + return $this->kicad_symbol; + } + + public function setKicadSymbol(?string $kicad_symbol): EDACategoryInfo + { + $this->kicad_symbol = $kicad_symbol; + return $this; + } + +} \ No newline at end of file diff --git a/src/Entity/EDA/EDAFootprintInfo.php b/src/Entity/EDA/EDAFootprintInfo.php new file mode 100644 index 00000000..9c5ef1c1 --- /dev/null +++ b/src/Entity/EDA/EDAFootprintInfo.php @@ -0,0 +1,51 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity\EDA; + +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Embeddable; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints\Length; + +#[Embeddable] +class EDAFootprintInfo +{ + /** @var string|null The KiCAD footprint, which should be used (the path to the library) */ + #[Column(type: Types::STRING, nullable: true)] + #[Groups(['full', 'footprint:read', 'footprint:write', 'import'])] + #[Length(max: 255)] + private ?string $kicad_footprint = null; + + public function getKicadFootprint(): ?string + { + return $this->kicad_footprint; + } + + public function setKicadFootprint(?string $kicad_footprint): EDAFootprintInfo + { + $this->kicad_footprint = $kicad_footprint; + return $this; + } +} \ No newline at end of file diff --git a/src/Entity/EDA/EDAPartInfo.php b/src/Entity/EDA/EDAPartInfo.php new file mode 100644 index 00000000..b4fc3588 --- /dev/null +++ b/src/Entity/EDA/EDAPartInfo.php @@ -0,0 +1,175 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity\EDA; + +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Embeddable; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints\Length; + +#[Embeddable] +class EDAPartInfo +{ + /** + * @var string|null The reference prefix of the Part in the schematic. E.g. "R" for resistors, or "C" for capacitors. + */ + #[Column(type: Types::STRING, nullable: true)] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] + #[Length(max: 255)] + private ?string $reference_prefix = null; + + /** @var string|null The value, which should be shown together with the part (e.g. 470 for a 470 Ohm resistor) */ + #[Column(type: Types::STRING, nullable: true)] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] + #[Length(max: 255)] + private ?string $value = null; + + /** @var bool|null Visibility of this part to EDA software in trinary logic. True=Visible, False=Invisible, Null=Auto */ + #[Column(name: 'invisible', type: Types::BOOLEAN, nullable: true)] //TODO: Rename column to visibility + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] + private ?bool $visibility = null; + + /** @var bool|null If this is set to true, then this part will be excluded from the BOM */ + #[Column(type: Types::BOOLEAN, nullable: true)] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] + private ?bool $exclude_from_bom = null; + + /** @var bool|null If this is set to true, then this part will be excluded from the board/the PCB */ + #[Column(type: Types::BOOLEAN, nullable: true)] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] + private ?bool $exclude_from_board = null; + + /** @var bool|null If this is set to true, then this part will be excluded in the simulation */ + #[Column(type: Types::BOOLEAN, nullable: true)] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] + private ?bool $exclude_from_sim = null; + + /** @var string|null The KiCAD schematic symbol, which should be used (the path to the library) */ + #[Column(type: Types::STRING, nullable: true)] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] + #[Length(max: 255)] + private ?string $kicad_symbol = null; + + /** @var string|null The KiCAD footprint, which should be used (the path to the library) */ + #[Column(type: Types::STRING, nullable: true)] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] + #[Length(max: 255)] + private ?string $kicad_footprint = null; + + public function __construct() + { + + } + + public function getReferencePrefix(): ?string + { + return $this->reference_prefix; + } + + public function setReferencePrefix(?string $reference_prefix): EDAPartInfo + { + $this->reference_prefix = $reference_prefix; + return $this; + } + + public function getValue(): ?string + { + return $this->value; + } + + public function setValue(?string $value): EDAPartInfo + { + $this->value = $value; + return $this; + } + + public function getVisibility(): ?bool + { + return $this->visibility; + } + + public function setVisibility(?bool $visibility): EDAPartInfo + { + $this->visibility = $visibility; + return $this; + } + + public function getExcludeFromBom(): ?bool + { + return $this->exclude_from_bom; + } + + public function setExcludeFromBom(?bool $exclude_from_bom): EDAPartInfo + { + $this->exclude_from_bom = $exclude_from_bom; + return $this; + } + + public function getExcludeFromBoard(): ?bool + { + return $this->exclude_from_board; + } + + public function setExcludeFromBoard(?bool $exclude_from_board): EDAPartInfo + { + $this->exclude_from_board = $exclude_from_board; + return $this; + } + + public function getExcludeFromSim(): ?bool + { + return $this->exclude_from_sim; + } + + public function setExcludeFromSim(?bool $exclude_from_sim): EDAPartInfo + { + $this->exclude_from_sim = $exclude_from_sim; + return $this; + } + + public function getKicadSymbol(): ?string + { + return $this->kicad_symbol; + } + + public function setKicadSymbol(?string $kicad_symbol): EDAPartInfo + { + $this->kicad_symbol = $kicad_symbol; + return $this; + } + + public function getKicadFootprint(): ?string + { + return $this->kicad_footprint; + } + + public function setKicadFootprint(?string $kicad_footprint): EDAPartInfo + { + $this->kicad_footprint = $kicad_footprint; + return $this; + } + + +} \ No newline at end of file diff --git a/src/Entity/InfoProviderSystem/BulkImportJobStatus.php b/src/Entity/InfoProviderSystem/BulkImportJobStatus.php new file mode 100644 index 00000000..7a88802f --- /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 00000000..0eedc553 --- /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 00000000..bc842a26 --- /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 00000000..90519561 --- /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/LabelSystem/BarcodeType.php b/src/Entity/LabelSystem/BarcodeType.php index 0794b606..daf7d401 100644 --- a/src/Entity/LabelSystem/BarcodeType.php +++ b/src/Entity/LabelSystem/BarcodeType.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LabelSystem; enum BarcodeType: string diff --git a/src/Entity/LabelSystem/LabelOptions.php b/src/Entity/LabelSystem/LabelOptions.php index 1d15d0f5..ee1a5414 100644 --- a/src/Entity/LabelSystem/LabelOptions.php +++ b/src/Entity/LabelSystem/LabelOptions.php @@ -43,6 +43,7 @@ namespace App\Entity\LabelSystem; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Validator\Constraints as Assert; #[ORM\Embeddable] @@ -53,6 +54,7 @@ class LabelOptions */ #[Assert\Positive] #[ORM\Column(type: Types::FLOAT)] + #[Groups(["extended", "full", "import"])] protected float $width = 50.0; /** @@ -60,38 +62,45 @@ class LabelOptions */ #[Assert\Positive] #[ORM\Column(type: Types::FLOAT)] + #[Groups(["extended", "full", "import"])] protected float $height = 30.0; /** * @var BarcodeType The type of the barcode that should be used in the label (e.g. 'qr') */ #[ORM\Column(type: Types::STRING, enumType: BarcodeType::class)] + #[Groups(["extended", "full", "import"])] protected BarcodeType $barcode_type = BarcodeType::NONE; /** * @var LabelPictureType What image should be shown along the label */ #[ORM\Column(type: Types::STRING, enumType: LabelPictureType::class)] + #[Groups(["extended", "full", "import"])] protected LabelPictureType $picture_type = LabelPictureType::NONE; #[ORM\Column(type: Types::STRING, enumType: LabelSupportedElement::class)] + #[Groups(["extended", "full", "import"])] protected LabelSupportedElement $supported_element = LabelSupportedElement::PART; /** * @var string any additional CSS for the label */ #[ORM\Column(type: Types::TEXT)] + #[Groups([ "full", "import"])] protected string $additional_css = ''; /** @var LabelProcessMode The mode that will be used to interpret the lines */ - #[ORM\Column(type: Types::STRING, enumType: LabelProcessMode::class, name: 'lines_mode')] + #[ORM\Column(name: 'lines_mode', type: Types::STRING, enumType: LabelProcessMode::class)] + #[Groups(["extended", "full", "import"])] protected LabelProcessMode $process_mode = LabelProcessMode::PLACEHOLDER; /** * @var string */ #[ORM\Column(type: Types::TEXT)] + #[Groups(["extended", "full", "import"])] protected string $lines = ''; public function getWidth(): float diff --git a/src/Entity/LabelSystem/LabelPictureType.php b/src/Entity/LabelSystem/LabelPictureType.php index c9183ca6..c1f90fe2 100644 --- a/src/Entity/LabelSystem/LabelPictureType.php +++ b/src/Entity/LabelSystem/LabelPictureType.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LabelSystem; enum LabelPictureType: string @@ -34,4 +36,4 @@ enum LabelPictureType: string * Show the main attachment of the element on the label */ case MAIN_ATTACHMENT = 'main_attachment'; -} \ No newline at end of file +} diff --git a/src/Entity/LabelSystem/LabelProcessMode.php b/src/Entity/LabelSystem/LabelProcessMode.php index 76bf175f..d5967b49 100644 --- a/src/Entity/LabelSystem/LabelProcessMode.php +++ b/src/Entity/LabelSystem/LabelProcessMode.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LabelSystem; enum LabelProcessMode: string @@ -26,4 +28,4 @@ enum LabelProcessMode: string case PLACEHOLDER = 'html'; /** Interpret the given lines as twig template */ case TWIG = 'twig'; -} \ No newline at end of file +} diff --git a/src/Entity/LabelSystem/LabelProfile.php b/src/Entity/LabelSystem/LabelProfile.php index a504ee0f..d3616c34 100644 --- a/src/Entity/LabelSystem/LabelProfile.php +++ b/src/Entity/LabelSystem/LabelProfile.php @@ -41,8 +41,8 @@ declare(strict_types=1); namespace App\Entity\LabelSystem; +use Doctrine\Common\Collections\Criteria; use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\LabelProfileRepository; use App\EntityListeners\TreeCacheInvalidationListener; use Doctrine\DBAL\Types\Types; @@ -52,6 +52,7 @@ use App\Entity\Attachments\LabelAttachment; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Validator\Constraints as Assert; /** @@ -66,11 +67,11 @@ class LabelProfile extends AttachmentContainingDBElement /** * @var Collection */ - #[ORM\OneToMany(targetEntity: LabelAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: LabelAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $attachments; - #[ORM\ManyToOne(targetEntity: AttachmentTypeAttachment::class)] + #[ORM\ManyToOne(targetEntity: LabelAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] protected ?Attachment $master_picture_attachment = null; @@ -79,6 +80,7 @@ class LabelProfile extends AttachmentContainingDBElement */ #[Assert\Valid] #[ORM\Embedded(class: 'LabelOptions')] + #[Groups(["extended", "full", "import"])] protected LabelOptions $options; /** @@ -91,6 +93,7 @@ class LabelProfile extends AttachmentContainingDBElement * @var bool determines, if this label profile should be shown in the dropdown quick menu */ #[ORM\Column(type: Types::BOOLEAN)] + #[Groups(["extended", "full", "import"])] protected bool $show_in_dropdown = true; public function __construct() diff --git a/src/Entity/LabelSystem/LabelSupportedElement.php b/src/Entity/LabelSystem/LabelSupportedElement.php index 99bac6c9..7649e586 100644 --- a/src/Entity/LabelSystem/LabelSupportedElement.php +++ b/src/Entity/LabelSystem/LabelSupportedElement.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LabelSystem; +use App\Entity\Base\AbstractDBElement; +use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; enum LabelSupportedElement: string { @@ -32,14 +36,14 @@ enum LabelSupportedElement: string /** * Returns the entity class for the given element type - * @return string + * @return class-string */ public function getEntityClass(): string { return match ($this) { self::PART => Part::class, self::PART_LOT => PartLot::class, - self::STORELOCATION => Storelocation::class, + self::STORELOCATION => StorageLocation::class, }; } -} \ No newline at end of file +} diff --git a/src/Entity/LogSystem/AbstractLogEntry.php b/src/Entity/LogSystem/AbstractLogEntry.php index 1041cd6d..aa795613 100644 --- a/src/Entity/LogSystem/AbstractLogEntry.php +++ b/src/Entity/LogSystem/AbstractLogEntry.php @@ -23,30 +23,10 @@ declare(strict_types=1); namespace App\Entity\LogSystem; use Doctrine\DBAL\Types\Types; -use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractDBElement; -use App\Entity\ProjectSystem\Project; -use App\Entity\ProjectSystem\ProjectBOMEntry; -use App\Entity\LabelSystem\LabelProfile; -use App\Entity\Parameters\AbstractParameter; -use App\Entity\Parts\Category; -use App\Entity\Parts\Footprint; -use App\Entity\Parts\Manufacturer; -use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\Part; -use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; -use App\Entity\Parts\Supplier; -use App\Entity\PriceInformations\Currency; -use App\Entity\PriceInformations\Orderdetail; -use App\Entity\PriceInformations\Pricedetail; -use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; -use DateTime; + use Doctrine\ORM\Mapping as ORM; -use InvalidArgumentException; -use Psr\Log\LogLevel as PsrLogLevel; use App\Repository\LogEntryRepository; /** @@ -75,10 +55,11 @@ abstract class AbstractLogEntry extends AbstractDBElement #[ORM\Column(type: Types::STRING)] protected string $username = ''; - /** @var \DateTimeInterface The datetime the event associated with this log entry has occured + /** + * @var \DateTimeImmutable The datetime the event associated with this log entry has occured */ - #[ORM\Column(name: 'datetime', type: Types::DATETIME_MUTABLE)] - protected \DateTimeInterface $timestamp; + #[ORM\Column(name: 'datetime', type: Types::DATETIME_IMMUTABLE)] + protected \DateTimeImmutable $timestamp; /** * @var LogLevel The priority level of the associated level. 0 is highest, 7 lowest @@ -109,7 +90,7 @@ abstract class AbstractLogEntry extends AbstractDBElement public function __construct() { - $this->timestamp = new DateTime(); + $this->timestamp = new \DateTimeImmutable(); } /** @@ -184,7 +165,7 @@ abstract class AbstractLogEntry extends AbstractDBElement /** * Returns the timestamp when the event that caused this log entry happened. */ - public function getTimestamp(): \DateTimeInterface + public function getTimestamp(): \DateTimeImmutable { return $this->timestamp; } @@ -194,7 +175,7 @@ abstract class AbstractLogEntry extends AbstractDBElement * * @return $this */ - public function setTimestamp(\DateTimeInterface $timestamp): self + public function setTimestamp(\DateTimeImmutable $timestamp): self { $this->timestamp = $timestamp; diff --git a/src/Entity/LogSystem/CollectionElementDeleted.php b/src/Entity/LogSystem/CollectionElementDeleted.php index c3980a40..34ab8fba 100644 --- a/src/Entity/LogSystem/CollectionElementDeleted.php +++ b/src/Entity/LogSystem/CollectionElementDeleted.php @@ -46,18 +46,21 @@ use App\Entity\Attachments\AttachmentType; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Entity\Attachments\CategoryAttachment; use App\Entity\Attachments\CurrencyAttachment; +use App\Entity\Attachments\PartCustomStateAttachment; use App\Entity\Attachments\ProjectAttachment; use App\Entity\Attachments\FootprintAttachment; use App\Entity\Attachments\GroupAttachment; use App\Entity\Attachments\ManufacturerAttachment; use App\Entity\Attachments\MeasurementUnitAttachment; use App\Entity\Attachments\PartAttachment; -use App\Entity\Attachments\StorelocationAttachment; +use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Attachments\SupplierAttachment; use App\Entity\Attachments\UserAttachment; use App\Entity\Base\AbstractDBElement; 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; @@ -69,20 +72,19 @@ use App\Entity\Parameters\GroupParameter; use App\Entity\Parameters\ManufacturerParameter; use App\Entity\Parameters\MeasurementUnitParameter; use App\Entity\Parameters\PartParameter; -use App\Entity\Parameters\StorelocationParameter; +use App\Entity\Parameters\StorageLocationParameter; use App\Entity\Parameters\SupplierParameter; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use Doctrine\ORM\Mapping as ORM; -use InvalidArgumentException; #[ORM\Entity] class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventUndoInterface @@ -147,65 +149,40 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU private function resolveAbstractClassToInstantiableClass(string $abstract_class): string { if (is_a($abstract_class, AbstractParameter::class, true)) { - switch ($this->getTargetClass()) { - case AttachmentType::class: - return AttachmentTypeParameter::class; - case Category::class: - return CategoryParameter::class; - case Currency::class: - return CurrencyParameter::class; - case Project::class: - return ProjectParameter::class; - case Footprint::class: - return FootprintParameter::class; - case Group::class: - return GroupParameter::class; - case Manufacturer::class: - return ManufacturerParameter::class; - case MeasurementUnit::class: - return MeasurementUnitParameter::class; - case Part::class: - return PartParameter::class; - case Storelocation::class: - return StorelocationParameter::class; - case Supplier::class: - return SupplierParameter::class; - - default: - throw new \RuntimeException('Unknown target class for parameter: '.$this->getTargetClass()); - } + return match ($this->getTargetClass()) { + AttachmentType::class => AttachmentTypeParameter::class, + Category::class => CategoryParameter::class, + Currency::class => CurrencyParameter::class, + Project::class => ProjectParameter::class, + Footprint::class => FootprintParameter::class, + Group::class => GroupParameter::class, + Manufacturer::class => ManufacturerParameter::class, + MeasurementUnit::class => MeasurementUnitParameter::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()), + }; } if (is_a($abstract_class, Attachment::class, true)) { - switch ($this->getTargetClass()) { - case AttachmentType::class: - return AttachmentTypeAttachment::class; - case Category::class: - return CategoryAttachment::class; - case Currency::class: - return CurrencyAttachment::class; - case Project::class: - return ProjectAttachment::class; - case Footprint::class: - return FootprintAttachment::class; - case Group::class: - return GroupAttachment::class; - case Manufacturer::class: - return ManufacturerAttachment::class; - case MeasurementUnit::class: - return MeasurementUnitAttachment::class; - case Part::class: - return PartAttachment::class; - case Storelocation::class: - return StorelocationAttachment::class; - case Supplier::class: - return SupplierAttachment::class; - case User::class: - return UserAttachment::class; - - default: - throw new \RuntimeException('Unknown target class for parameter: '.$this->getTargetClass()); - } + return match ($this->getTargetClass()) { + AttachmentType::class => AttachmentTypeAttachment::class, + Category::class => CategoryAttachment::class, + Currency::class => CurrencyAttachment::class, + Project::class => ProjectAttachment::class, + Footprint::class => FootprintAttachment::class, + Group::class => GroupAttachment::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, + default => throw new \RuntimeException('Unknown target class for parameter: '.$this->getTargetClass()), + }; } throw new \RuntimeException('The class '.$abstract_class.' is abstract and no explicit resolving to an concrete type is defined!'); diff --git a/src/Entity/LogSystem/ElementCreatedLogEntry.php b/src/Entity/LogSystem/ElementCreatedLogEntry.php index 7d5968a8..8364974c 100644 --- a/src/Entity/LogSystem/ElementCreatedLogEntry.php +++ b/src/Entity/LogSystem/ElementCreatedLogEntry.php @@ -27,9 +27,7 @@ use App\Entity\Contracts\LogWithCommentInterface; use App\Entity\Contracts\LogWithEventUndoInterface; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; -use App\Services\LogSystem\EventUndoMode; use Doctrine\ORM\Mapping as ORM; -use InvalidArgumentException; #[ORM\Entity] class ElementCreatedLogEntry extends AbstractLogEntry implements LogWithCommentInterface, LogWithEventUndoInterface diff --git a/src/Entity/LogSystem/ElementDeletedLogEntry.php b/src/Entity/LogSystem/ElementDeletedLogEntry.php index 836e8d60..e3dd2ac7 100644 --- a/src/Entity/LogSystem/ElementDeletedLogEntry.php +++ b/src/Entity/LogSystem/ElementDeletedLogEntry.php @@ -29,9 +29,7 @@ use App\Entity\Contracts\NamedElementInterface; use App\Entity\Contracts\TimeTravelInterface; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; -use App\Services\LogSystem\EventUndoMode; use Doctrine\ORM\Mapping as ORM; -use InvalidArgumentException; #[ORM\Entity] class ElementDeletedLogEntry extends AbstractLogEntry implements TimeTravelInterface, LogWithCommentInterface, LogWithEventUndoInterface diff --git a/src/Entity/LogSystem/ElementEditedLogEntry.php b/src/Entity/LogSystem/ElementEditedLogEntry.php index 4279ac63..8d4b7b9d 100644 --- a/src/Entity/LogSystem/ElementEditedLogEntry.php +++ b/src/Entity/LogSystem/ElementEditedLogEntry.php @@ -28,7 +28,6 @@ use App\Entity\Contracts\LogWithEventUndoInterface; use App\Entity\Contracts\LogWithNewDataInterface; use App\Entity\Contracts\TimeTravelInterface; use Doctrine\ORM\Mapping as ORM; -use InvalidArgumentException; #[ORM\Entity] class ElementEditedLogEntry extends AbstractLogEntry implements TimeTravelInterface, LogWithCommentInterface, LogWithEventUndoInterface, LogWithNewDataInterface diff --git a/src/Entity/LogSystem/LogLevel.php b/src/Entity/LogSystem/LogLevel.php index 98fb3649..435c5468 100644 --- a/src/Entity/LogSystem/LogLevel.php +++ b/src/Entity/LogSystem/LogLevel.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LogSystem; -use \Psr\Log\LogLevel as PSRLogLevel; +use Psr\Log\LogLevel as PSRLogLevel; enum LogLevel: int { diff --git a/src/Entity/LogSystem/LogTargetType.php b/src/Entity/LogSystem/LogTargetType.php index 38d1f1ed..3b2d8682 100644 --- a/src/Entity/LogSystem/LogTargetType.php +++ b/src/Entity/LogSystem/LogTargetType.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LogSystem; use App\Entity\Attachments\Attachment; 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; @@ -29,8 +33,10 @@ use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; +use App\Entity\Parts\PartAssociation; +use App\Entity\Parts\PartCustomState; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; @@ -63,6 +69,11 @@ enum LogTargetType: int case PARAMETER = 18; 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. * @return string|null @@ -81,7 +92,7 @@ enum LogTargetType: int self::GROUP => Group::class, self::MANUFACTURER => Manufacturer::class, self::PART => Part::class, - self::STORELOCATION => Storelocation::class, + self::STORELOCATION => StorageLocation::class, self::SUPPLIER => Supplier::class, self::PART_LOT => PartLot::class, self::CURRENCY => Currency::class, @@ -90,6 +101,10 @@ enum LogTargetType: int self::MEASUREMENT_UNIT => MeasurementUnit::class, 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 }; } @@ -116,7 +131,7 @@ enum LogTargetType: int } } - $elementClass = is_object($element) ? get_class($element) : $element; + $elementClass = is_object($element) ? $element::class : $element; //If no matching type was found, throw an exception throw new \InvalidArgumentException("The given class $elementClass is not a valid log target type."); } diff --git a/src/Entity/LogSystem/LogWithEventUndoTrait.php b/src/Entity/LogSystem/LogWithEventUndoTrait.php index 16568241..ed8629dc 100644 --- a/src/Entity/LogSystem/LogWithEventUndoTrait.php +++ b/src/Entity/LogSystem/LogWithEventUndoTrait.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LogSystem; use App\Entity\Contracts\LogWithEventUndoInterface; @@ -48,4 +50,4 @@ trait LogWithEventUndoTrait $mode_int = $this->extra['um'] ?? 1; return EventUndoMode::fromExtraInt($mode_int); } -} \ No newline at end of file +} diff --git a/src/Entity/LogSystem/PartStockChangeType.php b/src/Entity/LogSystem/PartStockChangeType.php index bbd574a7..f69fe95f 100644 --- a/src/Entity/LogSystem/PartStockChangeType.php +++ b/src/Entity/LogSystem/PartStockChangeType.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LogSystem; enum PartStockChangeType: string @@ -39,6 +41,11 @@ enum PartStockChangeType: string }; } + public function toTranslationKey(): string + { + return 'log.part_stock_changed.' . $this->value; + } + public static function fromExtraShortType(string $value): self { return match ($value) { @@ -48,4 +55,4 @@ enum PartStockChangeType: string default => throw new \InvalidArgumentException("Invalid short type: $value"), }; } -} \ No newline at end of file +} diff --git a/src/Entity/LogSystem/PartStockChangedLogEntry.php b/src/Entity/LogSystem/PartStockChangedLogEntry.php index 63288d0d..1bac9e9f 100644 --- a/src/Entity/LogSystem/PartStockChangedLogEntry.php +++ b/src/Entity/LogSystem/PartStockChangedLogEntry.php @@ -41,8 +41,10 @@ class PartStockChangedLogEntry extends AbstractLogEntry * @param float $new_total_part_instock The new total instock of the part. * @param string $comment The comment associated with the change. * @param PartLot|null $move_to_target The target lot if the type is TYPE_MOVE. + * @param \DateTimeInterface|null $action_timestamp The optional timestamp, where the action happened. Useful if the action happened in the past, and the log entry is created afterwards. */ - protected function __construct(PartStockChangeType $type, PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, ?PartLot $move_to_target = null) + protected function __construct(PartStockChangeType $type, PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, ?PartLot $move_to_target = null, + ?\DateTimeInterface $action_timestamp = null) { parent::__construct(); @@ -50,8 +52,6 @@ class PartStockChangedLogEntry extends AbstractLogEntry $this->level = LogLevel::INFO; $this->setTargetElement($lot); - - $this->typeString = 'part_stock_changed'; $this->extra = array_merge($this->extra, [ 't' => $type->toExtraShortType(), 'o' => $old_stock, @@ -62,6 +62,11 @@ class PartStockChangedLogEntry extends AbstractLogEntry $this->extra['c'] = mb_strimwidth($comment, 0, self::COMMENT_MAX_LENGTH, '...'); } + if ($action_timestamp instanceof \DateTimeInterface) { + //The action timestamp is saved as an ISO 8601 string + $this->extra['a'] = $action_timestamp->format(\DateTimeInterface::ATOM); + } + if ($move_to_target instanceof PartLot) { if ($type !== PartStockChangeType::MOVE) { throw new \InvalidArgumentException('The move_to_target parameter can only be set if the type is "move"!'); @@ -78,11 +83,12 @@ class PartStockChangedLogEntry extends AbstractLogEntry * @param float $new_stock The new stock of the lot. * @param float $new_total_part_instock The new total instock of the part. * @param string $comment The comment associated with the change. + * @param \DateTimeInterface|null $action_timestamp The optional timestamp, where the action happened. Useful if the action happened in the past, and the log entry is created afterwards. * @return self */ - public static function add(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment): self + public static function add(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, ?\DateTimeInterface $action_timestamp = null): self { - return new self(PartStockChangeType::ADD, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment); + return new self(PartStockChangeType::ADD, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment, action_timestamp: $action_timestamp); } /** @@ -92,11 +98,12 @@ class PartStockChangedLogEntry extends AbstractLogEntry * @param float $new_stock The new stock of the lot. * @param float $new_total_part_instock The new total instock of the part. * @param string $comment The comment associated with the change. + * @param \DateTimeInterface|null $action_timestamp The optional timestamp, where the action happened. Useful if the action happened in the past, and the log entry is created afterwards. * @return self */ - public static function withdraw(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment): self + public static function withdraw(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, ?\DateTimeInterface $action_timestamp = null): self { - return new self(PartStockChangeType::WITHDRAW, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment); + return new self(PartStockChangeType::WITHDRAW, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment, action_timestamp: $action_timestamp); } /** @@ -107,10 +114,12 @@ class PartStockChangedLogEntry extends AbstractLogEntry * @param float $new_total_part_instock The new total instock of the part. * @param string $comment The comment associated with the change. * @param PartLot $move_to_target The target lot. + * @param \DateTimeInterface|null $action_timestamp The optional timestamp, where the action happened. Useful if the action happened in the past, and the log entry is created afterwards. + * @return self */ - public static function move(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, PartLot $move_to_target): self + public static function move(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, PartLot $move_to_target, ?\DateTimeInterface $action_timestamp = null): self { - return new self(PartStockChangeType::MOVE, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment, $move_to_target); + return new self(PartStockChangeType::MOVE, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment, $move_to_target, action_timestamp: $action_timestamp); } /** @@ -169,4 +178,18 @@ class PartStockChangedLogEntry extends AbstractLogEntry { return $this->extra['m'] ?? null; } + + /** + * Returns the timestamp when this action was performed and not when the log entry was created. + * This is useful if the action happened in the past, and the log entry is created afterwards. + * If the timestamp is not set, null is returned. + * @return \DateTimeInterface|null + */ + public function getActionTimestamp(): ?\DateTimeInterface + { + if (!empty($this->extra['a'])) { + return \DateTimeImmutable::createFromFormat(\DateTimeInterface::ATOM, $this->extra['a']); + } + return null; + } } diff --git a/src/Entity/LogSystem/SecurityEventLogEntry.php b/src/Entity/LogSystem/SecurityEventLogEntry.php index ffcfd6a5..12e8e65e 100644 --- a/src/Entity/LogSystem/SecurityEventLogEntry.php +++ b/src/Entity/LogSystem/SecurityEventLogEntry.php @@ -44,9 +44,9 @@ namespace App\Entity\LogSystem; use App\Entity\Base\AbstractDBElement; use App\Entity\UserSystem\User; use App\Events\SecurityEvents; +use App\Helpers\IPAnonymizer; use Doctrine\ORM\Mapping as ORM; use InvalidArgumentException; -use Symfony\Component\HttpFoundation\IpUtils; /** * This log entry is created when something security related to a user happens. @@ -127,14 +127,14 @@ class SecurityEventLogEntry extends AbstractLogEntry * Sets the IP address used to log in the user. * * @param string $ip the IP address used to log in the user - * @param bool $anonymize Anonymize the IP address (remove last block) to be GPDR compliant + * @param bool $anonymize Anonymize the IP address (remove last block) to be GDPR compliant * * @return $this */ public function setIPAddress(string $ip, bool $anonymize = true): self { if ($anonymize) { - $ip = IpUtils::anonymize($ip); + $ip = IPAnonymizer::anonymize($ip); } $this->extra['i'] = $ip; diff --git a/src/Entity/LogSystem/UserLoginLogEntry.php b/src/Entity/LogSystem/UserLoginLogEntry.php index c9e6bc21..0719a740 100644 --- a/src/Entity/LogSystem/UserLoginLogEntry.php +++ b/src/Entity/LogSystem/UserLoginLogEntry.php @@ -22,8 +22,9 @@ declare(strict_types=1); namespace App\Entity\LogSystem; +use App\Helpers\IPAnonymizer; use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\HttpFoundation\IpUtils; + /** * This log entry is created when a user logs in. @@ -52,14 +53,14 @@ class UserLoginLogEntry extends AbstractLogEntry * Sets the IP address used to log in the user. * * @param string $ip the IP address used to log in the user - * @param bool $anonymize Anonymize the IP address (remove last block) to be GPDR compliant + * @param bool $anonymize Anonymize the IP address (remove last block) to be GDPR compliant * * @return $this */ public function setIPAddress(string $ip, bool $anonymize = true): self { if ($anonymize) { - $ip = IpUtils::anonymize($ip); + $ip = IPAnonymizer::anonymize($ip); } $this->extra['i'] = $ip; diff --git a/src/Entity/LogSystem/UserLogoutLogEntry.php b/src/Entity/LogSystem/UserLogoutLogEntry.php index ba52de87..f9f9a3dc 100644 --- a/src/Entity/LogSystem/UserLogoutLogEntry.php +++ b/src/Entity/LogSystem/UserLogoutLogEntry.php @@ -22,8 +22,8 @@ declare(strict_types=1); namespace App\Entity\LogSystem; +use App\Helpers\IPAnonymizer; use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\HttpFoundation\IpUtils; #[ORM\Entity] class UserLogoutLogEntry extends AbstractLogEntry @@ -49,14 +49,14 @@ class UserLogoutLogEntry extends AbstractLogEntry * Sets the IP address used to log in the user. * * @param string $ip the IP address used to log in the user - * @param bool $anonymize Anonymize the IP address (remove last block) to be GPDR compliant + * @param bool $anonymize Anonymize the IP address (remove last block) to be GDPR compliant * * @return $this */ public function setIPAddress(string $ip, bool $anonymize = true): self { if ($anonymize) { - $ip = IpUtils::anonymize($ip); + $ip = IPAnonymizer::anonymize($ip); } $this->extra['i'] = $ip; diff --git a/src/Entity/OAuthToken.php b/src/Entity/OAuthToken.php index b1534a4d..bc692369 100644 --- a/src/Entity/OAuthToken.php +++ b/src/Entity/OAuthToken.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace App\Entity; -use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractNamedDBElement; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; @@ -32,22 +31,22 @@ use League\OAuth2\Client\Token\AccessTokenInterface; /** * This entity represents a OAuth token pair (access and refresh token), for an application */ -#[ORM\Entity()] +#[ORM\Entity] #[ORM\Table(name: 'oauth_tokens')] #[ORM\UniqueConstraint(name: 'oauth_tokens_unique_name', columns: ['name'])] #[ORM\Index(columns: ['name'], name: 'oauth_tokens_name_idx')] class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface { /** @var string|null The short-term usable OAuth2 token */ - #[ORM\Column(type: 'text', nullable: true)] + #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $token = null; - /** @var \DateTimeInterface The date when the token expires */ + /** @var \DateTimeImmutable|null The date when the token expires */ #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] - private ?\DateTimeInterface $expires_at = null; + private ?\DateTimeImmutable $expires_at = null; /** @var string|null The refresh token for the OAuth2 auth */ - #[ORM\Column(type: 'text', nullable: true)] + #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $refresh_token = null; /** @@ -55,7 +54,7 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface */ private const DEFAULT_EXPIRATION_TIME = 3600; - public function __construct(string $name, ?string $refresh_token, ?string $token = null, \DateTimeInterface $expires_at = null) + public function __construct(string $name, ?string $refresh_token, ?string $token = null, ?\DateTimeImmutable $expires_at = null) { //If token is given, you also have to give the expires_at date if ($token !== null && $expires_at === null) { @@ -83,7 +82,7 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface ); } - private static function unixTimestampToDatetime(int $timestamp): \DateTimeInterface + private static function unixTimestampToDatetime(int $timestamp): \DateTimeImmutable { return \DateTimeImmutable::createFromFormat('U', (string)$timestamp); } @@ -93,7 +92,7 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface return $this->token; } - public function getExpirationDate(): ?\DateTimeInterface + public function getExpirationDate(): ?\DateTimeImmutable { return $this->expires_at; } @@ -135,17 +134,17 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface $this->expires_at = self::unixTimestampToDatetime($accessToken->getExpires() ?? time() + self::DEFAULT_EXPIRATION_TIME); } - public function getExpires() + public function getExpires(): ?int { return $this->expires_at->getTimestamp(); } - public function hasExpired() + public function hasExpired(): bool { return $this->isExpired(); } - public function getValues() + public function getValues(): array { return []; } diff --git a/src/Entity/Parameters/AbstractParameter.php b/src/Entity/Parameters/AbstractParameter.php index 4a5cb40a..d84e68ad 100644 --- a/src/Entity/Parameters/AbstractParameter.php +++ b/src/Entity/Parameters/AbstractParameter.php @@ -41,7 +41,19 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Doctrine\Orm\Filter\RangeFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use App\ApiPlatform\Filter\LikeFilter; use App\Repository\ParameterRepository; +use App\Validator\UniqueValidatableInterface; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractNamedDBElement; @@ -49,6 +61,8 @@ 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 sprintf; @@ -56,13 +70,44 @@ use function sprintf; #[ORM\Entity(repositoryClass: ParameterRepository::class)] #[ORM\InheritanceType('SINGLE_TABLE')] #[ORM\DiscriminatorColumn(name: 'type', type: 'smallint')] -#[ORM\DiscriminatorMap([0 => 'CategoryParameter', 1 => 'CurrencyParameter', 2 => 'ProjectParameter', 3 => 'FootprintParameter', 4 => 'GroupParameter', 5 => 'ManufacturerParameter', 6 => 'MeasurementUnitParameter', 7 => 'PartParameter', 8 => 'StorelocationParameter', 9 => 'SupplierParameter', 10 => 'AttachmentTypeParameter'])] +#[ORM\DiscriminatorMap([0 => CategoryParameter::class, 1 => CurrencyParameter::class, 2 => ProjectParameter::class, + 3 => FootprintParameter::class, 4 => GroupParameter::class, 5 => ManufacturerParameter::class, + 6 => MeasurementUnitParameter::class, 7 => PartParameter::class, 8 => StorageLocationParameter::class, + 9 => SupplierParameter::class, 10 => AttachmentTypeParameter::class, + 12 => PartCustomStateParameter::class])] #[ORM\Table('parameters')] -#[ORM\Index(name: 'parameter_name_idx', columns: ['name'])] -#[ORM\Index(name: 'parameter_group_idx', columns: ['param_group'])] -#[ORM\Index(name: 'parameter_type_element_idx', columns: ['type', 'element_id'])] -abstract class AbstractParameter extends AbstractNamedDBElement +#[ORM\Index(columns: ['name'], name: 'parameter_name_idx')] +#[ORM\Index(columns: ['param_group'], name: 'parameter_group_idx')] +#[ORM\Index(columns: ['type', 'element_id'], name: 'parameter_type_element_idx')] +#[ApiResource( + shortName: 'Parameter', + operations: [ + new Get(security: 'is_granted("read", object)'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['parameter:read', 'parameter:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['parameter:write', 'parameter:write:standalone', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiFilter(LikeFilter::class, properties: ["name", "symbol", "unit", "group", "value_text"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(RangeFilter::class, properties: ["value_min", "value_typical", "value_max"])] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] +//This discriminator map is required for API platform to know which class to use for deserialization, when creating a new parameter. +#[DiscriminatorMap(typeProperty: '_type', mapping: self::API_DISCRIMINATOR_MAP)] +abstract class AbstractParameter extends AbstractNamedDBElement implements UniqueValidatableInterface { + + /* + * 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" => PartParameter::class, + "AttachmentType" => AttachmentTypeParameter::class, "Category" => CategoryParameter::class, "Currency" => CurrencyParameter::class, + "Project" => ProjectParameter::class, "Footprint" => FootprintParameter::class, "Group" => GroupParameter::class, + "Manufacturer" => ManufacturerParameter::class, "MeasurementUnit" => MeasurementUnitParameter::class, + "StorageLocation" => StorageLocationParameter::class, "Supplier" => SupplierParameter::class, "PartCustomState" => PartCustomStateParameter::class]; + /** * @var string The class of the element that can be passed to this attachment. Must be overridden in subclasses. */ @@ -72,56 +117,59 @@ abstract class AbstractParameter extends AbstractNamedDBElement * @var string The mathematical symbol for this specification. Can be rendered pretty later. Should be short */ #[Assert\Length(max: 20)] - #[Groups(['full'])] + #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(type: Types::STRING)] protected string $symbol = ''; /** * @var float|null the guaranteed minimum value of this property */ - #[Assert\Type(['float', null])] + #[Assert\Type(['float', 'null'])] #[Assert\LessThanOrEqual(propertyPath: 'value_typical', message: 'parameters.validator.min_lesser_typical')] #[Assert\LessThan(propertyPath: 'value_max', message: 'parameters.validator.min_lesser_max')] - #[Groups(['full'])] + #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $value_min = null; /** * @var float|null the typical value of this property */ - #[Assert\Type([null, 'float'])] - #[Groups(['full'])] + #[Assert\Type(['null', 'float'])] + #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $value_typical = null; /** * @var float|null the maximum value of this property */ - #[Assert\Type(['float', null])] + #[Assert\Type(['float', 'null'])] #[Assert\GreaterThanOrEqual(propertyPath: 'value_typical', message: 'parameters.validator.max_greater_typical')] - #[Groups(['full'])] + #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $value_max = null; /** * @var string The unit in which the value values are given (e.g. V) */ - #[Groups(['full'])] + #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(type: Types::STRING)] + #[Assert\Length(max: 50)] protected string $unit = ''; /** * @var string a text value for the given property */ - #[Groups(['full'])] + #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(type: Types::STRING)] + #[Assert\Length(max: 255)] protected string $value_text = ''; /** * @var string the group this parameter belongs to */ - #[Groups(['full'])] - #[ORM\Column(type: Types::STRING, name: 'param_group')] + #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] + #[ORM\Column(name: 'param_group', type: Types::STRING)] + #[Assert\Length(max: 255)] protected string $group = ''; /** @@ -129,6 +177,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement * * @var AbstractDBElement|null the element to which this parameter belongs to */ + #[Groups(['parameter:read:standalone', 'parameter:write:standalone'])] protected ?AbstractDBElement $element = null; public function __construct() @@ -158,7 +207,9 @@ abstract class AbstractParameter extends AbstractNamedDBElement * Return a formatted string version of the values of the string. * Based on the set values it can return something like this: 34 V (12 V ... 50 V) [Text]. */ - public function getFormattedValue(): string + #[Groups(['parameter:read', 'full'])] + #[SerializedName('formatted')] + 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) { @@ -167,20 +218,20 @@ abstract class AbstractParameter extends AbstractNamedDBElement $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 @@ -294,25 +345,25 @@ abstract class AbstractParameter extends AbstractNamedDBElement /** * 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); } /** @@ -391,11 +442,21 @@ abstract class AbstractParameter extends AbstractNamedDBElement /** * 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; @@ -409,4 +470,9 @@ abstract class AbstractParameter extends AbstractNamedDBElement { return static::ALLOWED_ELEMENT_CLASS; } + + public function getComparableFields(): array + { + return ['name' => $this->name, 'group' => $this->group, 'element' => $this->element?->getId()]; + } } diff --git a/src/Entity/Parameters/AttachmentTypeParameter.php b/src/Entity/Parameters/AttachmentTypeParameter.php index 2ac9dc09..9a272a7d 100644 --- a/src/Entity/Parameters/AttachmentTypeParameter.php +++ b/src/Entity/Parameters/AttachmentTypeParameter.php @@ -41,11 +41,13 @@ declare(strict_types=1); namespace App\Entity\Parameters; -use App\Repository\ParameterRepository; use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractDBElement; +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)] @@ -57,5 +59,6 @@ class AttachmentTypeParameter extends AbstractParameter */ #[ORM\ManyToOne(targetEntity: AttachmentType::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/Parameters/CategoryParameter.php b/src/Entity/Parameters/CategoryParameter.php index 8aa4f29c..ecab1740 100644 --- a/src/Entity/Parameters/CategoryParameter.php +++ b/src/Entity/Parameters/CategoryParameter.php @@ -41,11 +41,13 @@ declare(strict_types=1); namespace App\Entity\Parameters; -use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\Category; +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)] @@ -57,5 +59,6 @@ class CategoryParameter extends AbstractParameter */ #[ORM\ManyToOne(targetEntity: Category::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/Parameters/CurrencyParameter.php b/src/Entity/Parameters/CurrencyParameter.php index 3540f5cb..9ab09bed 100644 --- a/src/Entity/Parameters/CurrencyParameter.php +++ b/src/Entity/Parameters/CurrencyParameter.php @@ -41,11 +41,13 @@ declare(strict_types=1); namespace App\Entity\Parameters; -use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\PriceInformations\Currency; +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; /** * An attachment attached to a category element. @@ -61,5 +63,6 @@ class CurrencyParameter extends AbstractParameter */ #[ORM\ManyToOne(targetEntity: Currency::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/Parameters/FootprintParameter.php b/src/Entity/Parameters/FootprintParameter.php index 2a978b04..578ddef3 100644 --- a/src/Entity/Parameters/FootprintParameter.php +++ b/src/Entity/Parameters/FootprintParameter.php @@ -41,11 +41,13 @@ declare(strict_types=1); namespace App\Entity\Parameters; -use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\Footprint; +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)] @@ -58,5 +60,6 @@ class FootprintParameter extends AbstractParameter */ #[ORM\ManyToOne(targetEntity: Footprint::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/Parameters/GroupParameter.php b/src/Entity/Parameters/GroupParameter.php index 1bc23ea8..7fb5540f 100644 --- a/src/Entity/Parameters/GroupParameter.php +++ b/src/Entity/Parameters/GroupParameter.php @@ -41,11 +41,13 @@ declare(strict_types=1); namespace App\Entity\Parameters; -use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\UserSystem\Group; +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)] @@ -58,5 +60,6 @@ class GroupParameter extends AbstractParameter */ #[ORM\ManyToOne(targetEntity: Group::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/Parameters/ManufacturerParameter.php b/src/Entity/Parameters/ManufacturerParameter.php index 6f33dce8..883a78f4 100644 --- a/src/Entity/Parameters/ManufacturerParameter.php +++ b/src/Entity/Parameters/ManufacturerParameter.php @@ -41,11 +41,13 @@ declare(strict_types=1); namespace App\Entity\Parameters; -use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\Manufacturer; +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)] @@ -58,5 +60,6 @@ class ManufacturerParameter extends AbstractParameter */ #[ORM\ManyToOne(targetEntity: Manufacturer::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/Parameters/MeasurementUnitParameter.php b/src/Entity/Parameters/MeasurementUnitParameter.php index 7332474e..09ff81ec 100644 --- a/src/Entity/Parameters/MeasurementUnitParameter.php +++ b/src/Entity/Parameters/MeasurementUnitParameter.php @@ -41,11 +41,13 @@ declare(strict_types=1); namespace App\Entity\Parameters; -use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\MeasurementUnit; +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)] @@ -58,5 +60,6 @@ class MeasurementUnitParameter extends AbstractParameter */ #[ORM\ManyToOne(targetEntity: MeasurementUnit::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/Parameters/StorelocationParameter.php b/src/Entity/Parameters/PartCustomStateParameter.php similarity index 80% rename from src/Entity/Parameters/StorelocationParameter.php rename to src/Entity/Parameters/PartCustomStateParameter.php index 098d3a5e..ceedf7b4 100644 --- a/src/Entity/Parameters/StorelocationParameter.php +++ b/src/Entity/Parameters/PartCustomStateParameter.php @@ -41,22 +41,25 @@ declare(strict_types=1); namespace App\Entity\Parameters; -use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; -use App\Entity\Parts\Storelocation; +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 StorelocationParameter extends AbstractParameter +class PartCustomStateParameter extends AbstractParameter { - final public const ALLOWED_ELEMENT_CLASS = Storelocation::class; + final public const ALLOWED_ELEMENT_CLASS = PartCustomState::class; /** - * @var Storelocation the element this para is associated with + * @var PartCustomState the element this para is associated with */ - #[ORM\ManyToOne(targetEntity: Storelocation::class, inversedBy: 'parameters')] + #[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/Parameters/PartParameter.php b/src/Entity/Parameters/PartParameter.php index 6c150e6b..91b51c00 100644 --- a/src/Entity/Parameters/PartParameter.php +++ b/src/Entity/Parameters/PartParameter.php @@ -41,11 +41,13 @@ declare(strict_types=1); namespace App\Entity\Parameters; -use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\Part; +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; /** * @see \App\Tests\Entity\Parameters\PartParameterTest @@ -61,5 +63,6 @@ class PartParameter extends AbstractParameter */ #[ORM\ManyToOne(targetEntity: Part::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/Parameters/ProjectParameter.php b/src/Entity/Parameters/ProjectParameter.php index 7413ca8a..7c3907cd 100644 --- a/src/Entity/Parameters/ProjectParameter.php +++ b/src/Entity/Parameters/ProjectParameter.php @@ -41,11 +41,13 @@ declare(strict_types=1); namespace App\Entity\Parameters; -use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\ProjectSystem\Project; +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)] @@ -58,5 +60,6 @@ class ProjectParameter extends AbstractParameter */ #[ORM\ManyToOne(targetEntity: Project::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/Services/LabelSystem/Barcodes/BarcodeRedirector.php b/src/Entity/Parameters/StorageLocationParameter.php similarity index 50% rename from src/Services/LabelSystem/Barcodes/BarcodeRedirector.php rename to src/Entity/Parameters/StorageLocationParameter.php index 0eba0ed4..f5cc6415 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php +++ b/src/Entity/Parameters/StorageLocationParameter.php @@ -39,52 +39,27 @@ declare(strict_types=1); * along with this program. If not, see . */ -namespace App\Services\LabelSystem\Barcodes; +namespace App\Entity\Parameters; -use App\Entity\Parts\PartLot; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\EntityNotFoundException; -use InvalidArgumentException; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use App\Entity\Base\AbstractDBElement; +use App\Entity\Parts\StorageLocation; +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; -/** - * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeRedirectorTest - */ -final class BarcodeRedirector +#[UniqueEntity(fields: ['name', 'group', 'element'])] +#[ORM\Entity(repositoryClass: ParameterRepository::class)] +class StorageLocationParameter extends AbstractParameter { - public function __construct(private readonly UrlGeneratorInterface $urlGenerator, private readonly EntityManagerInterface $em) - { - } + final public const ALLOWED_ELEMENT_CLASS = StorageLocation::class; /** - * Determines the URL to which the user should be redirected, when scanning a QR code. - * - * @param string $type The type of the element that was scanned (e.g. 'part', 'lot', etc.) - * @param int $id The ID of the element that was scanned - * - * @return string the URL to which should be redirected - * - * @throws EntityNotFoundException + * @var StorageLocation the element this para is associated with */ - public function getRedirectURL(string $type, int $id): string - { - switch ($type) { - case 'part': - return $this->urlGenerator->generate('app_part_show', ['id' => $id]); - case 'lot': - //Try to determine the part to the given lot - $lot = $this->em->find(PartLot::class, $id); - if (!$lot instanceof PartLot) { - throw new EntityNotFoundException(); - } - - return $this->urlGenerator->generate('app_part_show', ['id' => $lot->getPart()->getID()]); - - case 'location': - return $this->urlGenerator->generate('part_list_store_location', ['id' => $id]); - - default: - throw new InvalidArgumentException('Unknown $type: '.$type); - } - } + #[ORM\ManyToOne(targetEntity: StorageLocation::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/Parameters/SupplierParameter.php b/src/Entity/Parameters/SupplierParameter.php index 5c87ac08..6e42206f 100644 --- a/src/Entity/Parameters/SupplierParameter.php +++ b/src/Entity/Parameters/SupplierParameter.php @@ -41,11 +41,13 @@ declare(strict_types=1); namespace App\Entity\Parameters; -use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\Supplier; +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)] @@ -54,9 +56,10 @@ class SupplierParameter extends AbstractParameter final public const ALLOWED_ELEMENT_CLASS = Supplier::class; /** - * @var Supplier the element this para is associated with + * @var Supplier the element this parameter is associated with */ #[ORM\ManyToOne(targetEntity: Supplier::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/AssociationType.php b/src/Entity/Parts/AssociationType.php new file mode 100644 index 00000000..52a56af2 --- /dev/null +++ b/src/Entity/Parts/AssociationType.php @@ -0,0 +1,46 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity\Parts; + +/** + * The values of this enums are used to describe how two parts are associated with each other. + */ +enum AssociationType: int +{ + /** A user definable association type, which can be described in the comment field */ + case OTHER = 0; + /** The owning part is compatible with the other part */ + case COMPATIBLE = 1; + /** The owning part supersedes the other part (owner is newer version) */ + case SUPERSEDES = 2; + + /** + * Returns the translation key for this association type. + * @return string + */ + public function getTranslationKey(): string + { + return 'part_association.type.' . strtolower($this->name); + } +} diff --git a/src/Entity/Parts/Category.php b/src/Entity/Parts/Category.php index ac810cf8..a9efd2fa 100644 --- a/src/Entity/Parts/Category.php +++ b/src/Entity/Parts/Category.php @@ -22,8 +22,24 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; +use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentTypeAttachment; +use App\Entity\EDA\EDACategoryInfo; use App\Repository\Parts\CategoryRepository; use Doctrine\DBAL\Types\Types; use Doctrine\Common\Collections\ArrayCollection; @@ -34,6 +50,8 @@ use App\Entity\Parameters\CategoryParameter; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\TypeInfo\Type\NullableType; +use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\Validator\Constraints as Assert; /** @@ -43,71 +61,111 @@ use Symfony\Component\Validator\Constraints as Assert; */ #[ORM\Entity(repositoryClass: CategoryRepository::class)] #[ORM\Table(name: '`categories`')] -#[ORM\Index(name: 'category_idx_name', columns: ['name'])] -#[ORM\Index(name: 'category_idx_parent_name', columns: ['parent_id', 'name'])] +#[ORM\Index(columns: ['name'], name: 'category_idx_name')] +#[ORM\Index(columns: ['parent_id', 'name'], name: 'category_idx_parent_name')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@categories.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['category:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['category:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/categories/{id}/children.{_format}', + operations: [ + new GetCollection( + openapi: new Operation(summary: 'Retrieves the children elements of a category.'), + security: 'is_granted("@categories.read")' + ) + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: Category::class) + ], + normalizationContext: ['groups' => ['category:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class Category extends AbstractPartsContainingDBElement { - #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['category:read', 'category:write'])] + #[ApiProperty(readableLink: false, writableLink: false, nativeType: new NullableType(new ObjectType(self::class)))] protected ?AbstractStructuralDBElement $parent = null; + #[Groups(['category:read', 'category:write'])] + protected string $comment = ''; + /** - * @var string + * @var string The hint which is shown as hint under the partname field, when a part is created in this category. */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::TEXT)] protected string $partname_hint = ''; /** - * @var string + * @var string The regular expression which is used to validate the partname of a part in this category. */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::TEXT)] protected string $partname_regex = ''; /** - * @var bool + * @var string The prefix for ipn generation for created parts in this category. */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] + #[ORM\Column(type: Types::STRING, length: 255, nullable: false, options: ['default' => ''])] + protected string $part_ipn_prefix = ''; + + /** + * @var bool Set to true, if the footprints should be disabled for parts this category (not implemented yet). + */ + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_footprints = false; /** - * @var bool + * @var bool Set to true, if the manufacturers should be disabled for parts this category (not implemented yet). */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_manufacturers = false; /** - * @var bool + * @var bool Set to true, if the autodatasheets should be disabled for parts this category (not implemented yet). */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_autodatasheets = false; /** - * @var bool + * @var bool Set to true, if the properties should be disabled for parts this category (not implemented yet). */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_properties = false; /** - * @var string + * @var string The default description for parts in this category. */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::TEXT)] protected string $default_description = ''; /** - * @var string + * @var string The default comment for parts in this category. */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::TEXT)] protected string $default_comment = ''; @@ -115,23 +173,43 @@ class Category extends AbstractPartsContainingDBElement * @var Collection */ #[Assert\Valid] - #[Groups(['full'])] - #[ORM\OneToMany(targetEntity: CategoryAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[Groups(['full', 'category:read', 'category:write'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: CategoryAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: CategoryAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['category:read', 'category:write'])] protected ?Attachment $master_picture_attachment = null; /** @var Collection */ #[Assert\Valid] - #[Groups(['full'])] - #[ORM\OneToMany(targetEntity: CategoryParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[Groups(['full', 'category:read', 'category:write'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: CategoryParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] protected Collection $parameters; + #[Groups(['category:read'])] + protected ?\DateTimeImmutable $addedDate = null; + #[Groups(['category:read'])] + protected ?\DateTimeImmutable $lastModified = null; + + #[Assert\Valid] + #[ORM\Embedded(class: EDACategoryInfo::class)] + #[Groups(['full', 'category:read', 'category:write'])] + protected EDACategoryInfo $eda_info; + + public function __construct() + { + parent::__construct(); + $this->children = new ArrayCollection(); + $this->attachments = new ArrayCollection(); + $this->parameters = new ArrayCollection(); + $this->eda_info = new EDACategoryInfo(); + } + public function getPartnameHint(): string { return $this->partname_hint; @@ -156,6 +234,16 @@ class Category extends AbstractPartsContainingDBElement return $this; } + public function getPartIpnPrefix(): string + { + return $this->part_ipn_prefix; + } + + public function setPartIpnPrefix(string $part_ipn_prefix): void + { + $this->part_ipn_prefix = $part_ipn_prefix; + } + public function isDisableFootprints(): bool { return $this->disable_footprints; @@ -224,14 +312,17 @@ class Category extends AbstractPartsContainingDBElement public function setDefaultComment(string $default_comment): self { $this->default_comment = $default_comment; - return $this; } - public function __construct() + + public function getEdaInfo(): EDACategoryInfo { - parent::__construct(); - $this->children = new ArrayCollection(); - $this->attachments = new ArrayCollection(); - $this->parameters = new ArrayCollection(); + return $this->eda_info; + } + + public function setEdaInfo(EDACategoryInfo $eda_info): Category + { + $this->eda_info = $eda_info; + return $this; } } diff --git a/src/Entity/Parts/Footprint.php b/src/Entity/Parts/Footprint.php index 4126a63b..251232c1 100644 --- a/src/Entity/Parts/Footprint.php +++ b/src/Entity/Parts/Footprint.php @@ -22,8 +22,24 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; +use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentTypeAttachment; +use App\Entity\EDA\EDAFootprintInfo; use App\Repository\Parts\FootprintRepository; use App\Entity\Base\AbstractStructuralDBElement; use Doctrine\Common\Collections\ArrayCollection; @@ -32,6 +48,9 @@ use App\Entity\Base\AbstractPartsContainingDBElement; use App\Entity\Parameters\FootprintParameter; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\TypeInfo\Type\NullableType; +use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\Validator\Constraints as Assert; /** @@ -41,28 +60,63 @@ use Symfony\Component\Validator\Constraints as Assert; */ #[ORM\Entity(repositoryClass: FootprintRepository::class)] #[ORM\Table('`footprints`')] -#[ORM\Index(name: 'footprint_idx_name', columns: ['name'])] -#[ORM\Index(name: 'footprint_idx_parent_name', columns: ['parent_id', 'name'])] +#[ORM\Index(columns: ['name'], name: 'footprint_idx_name')] +#[ORM\Index(columns: ['parent_id', 'name'], name: 'footprint_idx_parent_name')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@footprints.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['footprint:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['footprint:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/footprints/{id}/children.{_format}', + operations: [ + new GetCollection( + openapi: new Operation(summary: 'Retrieves the children elements of a footprint.'), + security: 'is_granted("@footprints.read")' + ) + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: Footprint::class) + ], + normalizationContext: ['groups' => ['footprint:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class Footprint extends AbstractPartsContainingDBElement { #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['footprint:read', 'footprint:write'])] + #[ApiProperty(readableLink: false, writableLink: false, nativeType: new NullableType(new ObjectType(self::class)))] protected ?AbstractStructuralDBElement $parent = null; - #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; + #[Groups(['footprint:read', 'footprint:write'])] + protected string $comment = ''; + /** * @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: FootprintAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: FootprintAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => Criteria::ASC])] + #[Groups(['footprint:read', 'footprint:write'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: FootprintAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['footprint:read', 'footprint:write'])] protected ?Attachment $master_picture_attachment = null; /** @@ -70,15 +124,36 @@ class Footprint extends AbstractPartsContainingDBElement */ #[ORM\ManyToOne(targetEntity: FootprintAttachment::class)] #[ORM\JoinColumn(name: 'id_footprint_3d')] + #[Groups(['footprint:read', 'footprint:write'])] protected ?FootprintAttachment $footprint_3d = null; /** @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: FootprintParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: FootprintParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] + #[Groups(['footprint:read', 'footprint:write'])] protected Collection $parameters; + #[Groups(['footprint:read'])] + protected ?\DateTimeImmutable $addedDate = null; + #[Groups(['footprint:read'])] + protected ?\DateTimeImmutable $lastModified = null; + + #[Assert\Valid] + #[ORM\Embedded(class: EDAFootprintInfo::class)] + #[Groups(['full', 'footprint:read', 'footprint:write'])] + protected EDAFootprintInfo $eda_info; + + public function __construct() + { + parent::__construct(); + $this->children = new ArrayCollection(); + $this->attachments = new ArrayCollection(); + $this->parameters = new ArrayCollection(); + $this->eda_info = new EDAFootprintInfo(); + } + /**************************************** * Getters ****************************************/ @@ -107,11 +182,15 @@ class Footprint extends AbstractPartsContainingDBElement return $this; } - public function __construct() + + public function getEdaInfo(): EDAFootprintInfo { - parent::__construct(); - $this->children = new ArrayCollection(); - $this->attachments = new ArrayCollection(); - $this->parameters = new ArrayCollection(); + return $this->eda_info; + } + + public function setEdaInfo(EDAFootprintInfo $eda_info): Footprint + { + $this->eda_info = $eda_info; + return $this; } } diff --git a/src/Entity/Parts/InfoProviderReference.php b/src/Entity/Parts/InfoProviderReference.php index 53b81a0a..810aef0c 100644 --- a/src/Entity/Parts/InfoProviderReference.php +++ b/src/Entity/Parts/InfoProviderReference.php @@ -27,30 +27,36 @@ use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Embeddable; +use Symfony\Component\Serializer\Annotation\Groups; /** * This class represents a reference to a info provider inside a part. + * @see \App\Tests\Entity\Parts\InfoProviderReferenceTest */ #[Embeddable] class InfoProviderReference { /** @var string|null The key referencing the provider used to get this part, or null if it was not provided by a data provider */ - #[Column(type: 'string', nullable: true)] + #[Column(type: Types::STRING, nullable: true)] + #[Groups(['provider_reference:read', 'full'])] private ?string $provider_key = null; /** @var string|null The id of this part inside the provider system or null if the part was not provided by a data provider */ - #[Column(type: 'string', nullable: true)] + #[Column(type: Types::STRING, nullable: true)] + #[Groups(['provider_reference:read', 'full'])] private ?string $provider_id = null; /** * @var string|null The url of this part inside the provider system or null if this info is not existing */ - #[Column(type: 'string', nullable: true)] + #[Column(type: Types::STRING, length: 2048, nullable: true)] + #[Groups(['provider_reference:read', 'full'])] private ?string $provider_url = null; - #[Column(type: Types::DATETIME_MUTABLE, nullable: true, options: ['default' => null])] - private ?\DateTimeInterface $last_updated = null; + #[Column(type: Types::DATETIME_IMMUTABLE, nullable: true, options: ['default' => null])] + #[Groups(['provider_reference:read', 'full'])] + private ?\DateTimeImmutable $last_updated = null; /** * Constructing is forbidden from outside. @@ -89,9 +95,8 @@ class InfoProviderReference /** * Gets the time, when the part was last time updated by the provider. - * @return \DateTimeInterface|null */ - public function getLastUpdated(): ?\DateTimeInterface + public function getLastUpdated(): ?\DateTimeImmutable { return $this->last_updated; } @@ -152,4 +157,4 @@ class InfoProviderReference $ref->last_updated = new \DateTimeImmutable(); return $ref; } -} \ No newline at end of file +} diff --git a/src/Entity/Parts/Manufacturer.php b/src/Entity/Parts/Manufacturer.php index 418b4084..f7bee4b8 100644 --- a/src/Entity/Parts/Manufacturer.php +++ b/src/Entity/Parts/Manufacturer.php @@ -22,8 +22,23 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; +use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\Parts\ManufacturerRepository; use App\Entity\Base\AbstractStructuralDBElement; use Doctrine\Common\Collections\ArrayCollection; @@ -32,6 +47,9 @@ use App\Entity\Base\AbstractCompany; use App\Entity\Parameters\ManufacturerParameter; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\TypeInfo\Type\NullableType; +use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\Validator\Constraints as Assert; /** @@ -41,35 +59,71 @@ use Symfony\Component\Validator\Constraints as Assert; */ #[ORM\Entity(repositoryClass: ManufacturerRepository::class)] #[ORM\Table('`manufacturers`')] -#[ORM\Index(name: 'manufacturer_name', columns: ['name'])] -#[ORM\Index(name: 'manufacturer_idx_parent_name', columns: ['parent_id', 'name'])] +#[ORM\Index(columns: ['name'], name: 'manufacturer_name')] +#[ORM\Index(columns: ['parent_id', 'name'], name: 'manufacturer_idx_parent_name')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@manufacturers.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['manufacturer:write', 'company:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/manufacturers/{id}/children.{_format}', + operations: [ + new GetCollection( + openapi: new Operation(summary: 'Retrieves the children elements of a manufacturer.'), + security: 'is_granted("@manufacturers.read")' + ) + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: Manufacturer::class) + ], + normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class Manufacturer extends AbstractCompany { #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['manufacturer:read', 'manufacturer:write'])] + #[ApiProperty(readableLink: false, writableLink: false, nativeType: new NullableType(new ObjectType(self::class)))] protected ?AbstractStructuralDBElement $parent = null; - #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; /** * @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: ManufacturerAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: ManufacturerAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => Criteria::ASC])] + #[Groups(['manufacturer:read', 'manufacturer:write'])] + #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: ManufacturerAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['manufacturer:read', 'manufacturer:write'])] + #[ApiProperty(readableLink: false, writableLink: true)] protected ?Attachment $master_picture_attachment = null; /** @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: ManufacturerParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: ManufacturerParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] + #[Groups(['manufacturer:read', 'manufacturer:write'])] + #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $parameters; public function __construct() { diff --git a/src/Entity/Parts/MeasurementUnit.php b/src/Entity/Parts/MeasurementUnit.php index 538d4c57..dfb7e1d4 100644 --- a/src/Entity/Parts/MeasurementUnit.php +++ b/src/Entity/Parts/MeasurementUnit.php @@ -22,8 +22,23 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; +use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\Parts\MeasurementUnitRepository; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractStructuralDBElement; @@ -35,7 +50,10 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\TypeInfo\Type\NullableType; +use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Constraints\Length; /** * This unit represents the unit in which the amount of parts in stock are measured. @@ -46,8 +64,36 @@ use Symfony\Component\Validator\Constraints as Assert; #[UniqueEntity('unit')] #[ORM\Entity(repositoryClass: MeasurementUnitRepository::class)] #[ORM\Table(name: '`measurement_units`')] -#[ORM\Index(name: 'unit_idx_name', columns: ['name'])] -#[ORM\Index(name: 'unit_idx_parent_name', columns: ['parent_id', 'name'])] +#[ORM\Index(columns: ['name'], name: 'unit_idx_name')] +#[ORM\Index(columns: ['parent_id', 'name'], name: 'unit_idx_parent_name')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@measurement_units.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['measurement_unit:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['measurement_unit:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/measurement_units/{id}/children.{_format}', + operations: [ + new GetCollection( + openapi: new Operation(summary: 'Retrieves the children elements of a MeasurementUnit.'), + security: 'is_granted("@measurement_units.read")' + ) + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: MeasurementUnit::class) + ], + normalizationContext: ['groups' => ['measurement_unit:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment", "unit"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class MeasurementUnit extends AbstractPartsContainingDBElement { /** @@ -55,16 +101,19 @@ class MeasurementUnit extends AbstractPartsContainingDBElement * or m (for meters). */ #[Assert\Length(max: 10)] - #[Groups(['extended', 'full', 'import'])] - #[ORM\Column(type: Types::STRING, name: 'unit', nullable: true)] + #[Groups(['simple', 'extended', 'full', 'import', 'measurement_unit:read', 'measurement_unit:write'])] + #[ORM\Column(name: 'unit', type: Types::STRING, nullable: true)] protected ?string $unit = null; + #[Groups(['measurement_unit:read', 'measurement_unit:write'])] + protected string $comment = ''; + /** * @var bool Determines if the amount value associated with this unit should be treated as integer. * Set to false, to measure continuous sizes likes masses or lengths. */ - #[Groups(['extended', 'full', 'import'])] - #[ORM\Column(type: Types::BOOLEAN, name: 'is_integer')] + #[Groups(['simple', 'extended', 'full', 'import', 'measurement_unit:read', 'measurement_unit:write'])] + #[ORM\Column(name: 'is_integer', type: Types::BOOLEAN)] protected bool $is_integer = false; /** @@ -72,37 +121,48 @@ class MeasurementUnit extends AbstractPartsContainingDBElement * Useful for sizes like meters. For this the unit must be set */ #[Assert\Expression('this.isUseSIPrefix() == false or this.getUnit() != null', message: 'validator.measurement_unit.use_si_prefix_needs_unit')] - #[Groups(['full', 'import'])] - #[ORM\Column(type: Types::BOOLEAN, name: 'use_si_prefix')] + #[Groups(['simple', 'full', 'import', 'measurement_unit:read', 'measurement_unit:write'])] + #[ORM\Column(name: 'use_si_prefix', type: Types::BOOLEAN)] protected bool $use_si_prefix = false; - #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent', cascade: ['persist'])] - #[ORM\OrderBy(['name' => 'ASC'])] + #[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(['measurement_unit:read', 'measurement_unit:write'])] + #[ApiProperty(readableLink: false, writableLink: false, nativeType: new NullableType(new ObjectType(self::class)))] protected ?AbstractStructuralDBElement $parent = null; /** * @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: MeasurementUnitAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: MeasurementUnitAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => Criteria::ASC])] + #[Groups(['measurement_unit:read', 'measurement_unit:write'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: MeasurementUnitAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['measurement_unit:read', 'measurement_unit:write'])] protected ?Attachment $master_picture_attachment = null; /** @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: MeasurementUnitParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: MeasurementUnitParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] + #[Groups(['measurement_unit:read', 'measurement_unit:write'])] protected Collection $parameters; + #[Groups(['measurement_unit:read'])] + protected ?\DateTimeImmutable $addedDate = null; + #[Groups(['measurement_unit:read'])] + protected ?\DateTimeImmutable $lastModified = null; + + /** * @return string */ diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 5916569b..d0a279e3 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -22,25 +22,45 @@ declare(strict_types=1); namespace App\Entity\Parts; -use App\Entity\Attachments\AttachmentTypeAttachment; -use App\Repository\PartRepository; -use Doctrine\DBAL\Types\Types; +use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; +use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Doctrine\Orm\Filter\RangeFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +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\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\Parts\PartTraits\ProjectTrait; +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; +use App\Entity\Parts\PartTraits\AssociationTrait; use App\Entity\Parts\PartTraits\BasicPropertyTrait; +use App\Entity\Parts\PartTraits\EDATrait; use App\Entity\Parts\PartTraits\InstockTrait; use App\Entity\Parts\PartTraits\ManufacturerTrait; use App\Entity\Parts\PartTraits\OrderTrait; -use DateTime; +use App\Entity\Parts\PartTraits\ProjectTrait; +use App\EntityListeners\TreeCacheInvalidationListener; +use App\Repository\PartRepository; +use App\Validator\Constraints\UniqueObjectCollection; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Mapping as ORM; -use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; @@ -54,12 +74,45 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; * @extends AttachmentContainingDBElement * @template-use ParametersTrait */ -#[UniqueEntity(fields: ['ipn'], message: 'part.ipn.must_be_unique')] #[ORM\Entity(repositoryClass: PartRepository::class)] +#[ORM\EntityListeners([TreeCacheInvalidationListener::class])] #[ORM\Table('`parts`')] -#[ORM\Index(name: 'parts_idx_datet_name_last_id_needs', columns: ['datetime_added', 'name', 'last_modified', 'id', 'needs_review'])] -#[ORM\Index(name: 'parts_idx_name', columns: ['name'])] -#[ORM\Index(name: 'parts_idx_ipn', columns: ['ipn'])] +#[ORM\Index(columns: ['datetime_added', 'name', 'last_modified', 'id', 'needs_review'], name: 'parts_idx_datet_name_last_id_needs')] +#[ORM\Index(columns: ['name'], name: 'parts_idx_name')] +#[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' + ], + 'openapi_definition_name' => 'Read', + ], security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@parts.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['part:write', 'api:basic:write', 'eda_info:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(EntityFilter::class, properties: ["category", "footprint", "manufacturer", "partUnit", "partCustomState"])] +#[ApiFilter(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(RangeFilter::class, properties: ["mass", "minamount"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class Part extends AttachmentContainingDBElement { use AdvancedPropertyTrait; @@ -70,13 +123,16 @@ class Part extends AttachmentContainingDBElement use OrderTrait; use ParametersTrait; use ProjectTrait; + use AssociationTrait; + use EDATrait; /** @var Collection */ #[Assert\Valid] - #[Groups(['full'])] - #[ORM\OneToMany(targetEntity: PartParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[Groups(['full', 'part:read', 'part:write', 'import'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: PartParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] + #[UniqueObjectCollection(fields: ['name', 'group', 'element'])] protected Collection $parameters; @@ -94,9 +150,9 @@ class Part extends AttachmentContainingDBElement * @var Collection */ #[Assert\Valid] - #[Groups(['full'])] - #[ORM\OneToMany(targetEntity: PartAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[Groups(['full', 'part:read', 'part:write'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: PartAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $attachments; /** @@ -105,8 +161,21 @@ class Part extends AttachmentContainingDBElement #[Assert\Expression('value == null or value.isPicture()', message: 'part.master_attachment.must_be_picture')] #[ORM\ManyToOne(targetEntity: PartAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['part:read', 'part:write'])] protected ?Attachment $master_picture_attachment = null; + #[Groups(['part:read'])] + protected ?\DateTimeImmutable $addedDate = null; + #[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() { $this->attachments = new ArrayCollection(); @@ -116,8 +185,13 @@ class Part extends AttachmentContainingDBElement $this->parameters = new ArrayCollection(); $this->project_bom_entries = new ArrayCollection(); + $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(); + $this->eda_info = new EDAPartInfo(); } public function __clone() @@ -144,8 +218,16 @@ class Part extends AttachmentContainingDBElement $this->addParameter(clone $parameter); } + //Deep clone the owned part associations (the owned ones make not much sense without the owner) + $ownedAssociations = $this->associated_parts_as_owner; + $this->associated_parts_as_owner = new ArrayCollection(); + foreach ($ownedAssociations as $association) { + $this->addAssociatedPartsAsOwner(clone $association); + } + //Deep clone info provider $this->providerReference = clone $this->providerReference; + $this->eda_info = clone $this->eda_info; } parent::__clone(); } @@ -164,4 +246,38 @@ class Part extends AttachmentContainingDBElement } } } + + /** + * Get all bulk import job parts for this part + * @return Collection + */ + public function getBulkImportJobParts(): Collection + { + return $this->bulkImportJobParts; + } + + /** + * Add a bulk import job part to this part + */ + public function addBulkImportJobPart(BulkInfoProviderImportJobPart $jobPart): self + { + if (!$this->bulkImportJobParts->contains($jobPart)) { + $this->bulkImportJobParts->add($jobPart); + $jobPart->setPart($this); + } + return $this; + } + + /** + * Remove a bulk import job part from this part + */ + public function removeBulkImportJobPart(BulkInfoProviderImportJobPart $jobPart): self + { + if ($this->bulkImportJobParts->removeElement($jobPart)) { + if ($jobPart->getPart() === $this) { + $jobPart->setPart(null); + } + } + return $this; + } } diff --git a/src/Entity/Parts/PartAssociation.php b/src/Entity/Parts/PartAssociation.php new file mode 100644 index 00000000..32017488 --- /dev/null +++ b/src/Entity/Parts/PartAssociation.php @@ -0,0 +1,235 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity\Parts; + +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\Contracts\TimeStampableInterface; +use App\Repository\DBElementRepository; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use App\Entity\Base\AbstractDBElement; +use App\Entity\Base\TimestampTrait; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Constraints\Length; + +/** + * This entity describes a part association, which is a semantic connection between two parts. + * For example, a part association can be used to describe that a part is a replacement for another part. + * @see \App\Tests\Entity\Parts\PartAssociationTest + */ +#[ORM\Entity(repositoryClass: DBElementRepository::class)] +#[ORM\HasLifecycleCallbacks] +#[UniqueEntity(fields: ['other', 'owner', 'type'], message: 'validator.part_association.already_exists')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@parts.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['part_assoc:read', 'part_assoc:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['part_assoc:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["other_type", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['comment', 'addedDate', 'lastModified'])] +class PartAssociation extends AbstractDBElement implements TimeStampableInterface +{ + use TimestampTrait; + + /** + * @var AssociationType The type of this association (how the two parts are related) + */ + #[ORM\Column(type: Types::SMALLINT, enumType: AssociationType::class)] + #[Groups(['part_assoc:read', 'part_assoc:write'])] + protected AssociationType $type = AssociationType::OTHER; + + /** + * @var string|null A user definable association type, which can be described in the comment field, which + * is used if the type is OTHER + */ + #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] + #[Assert\Expression("this.getType().value !== 0 or this.getOtherType() !== null", + message: 'validator.part_association.must_set_an_value_if_type_is_other')] + #[Groups(['part_assoc:read', 'part_assoc:write'])] + #[Length(max: 255)] + protected ?string $other_type = null; + + /** + * @var string|null A comment describing this association further. + */ + #[ORM\Column(type: Types::TEXT, nullable: true)] + #[Groups(['part_assoc:read', 'part_assoc:write'])] + protected ?string $comment = null; + + /** + * @var Part|null The part which "owns" this association, e.g. the part which is a replacement for another part + */ + #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'associated_parts_as_owner')] + #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] + #[Assert\NotNull] + #[Groups(['part_assoc:read:standalone', 'part_assoc:write'])] + protected ?Part $owner = null; + + /** + * @var Part|null The part which is "owned" by this association, e.g. the part which is replaced by another part + */ + #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'associated_parts_as_other')] + #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] + #[Assert\NotNull] + #[Assert\Expression("this.getOwner() !== this.getOther()", + message: 'validator.part_association.part_cannot_be_associated_with_itself')] + #[Groups(['part_assoc:read', 'part_assoc:write'])] + protected ?Part $other = null; + + /** + * Returns the (semantic) relation type of this association as an AssociationType enum value. + * If the type is set to OTHER, then the other_type field value is used for the user defined type. + * @return AssociationType + */ + public function getType(): AssociationType + { + return $this->type; + } + + /** + * Sets the (semantic) relation type of this association as an AssociationType enum value. + * @param AssociationType $type + * @return $this + */ + public function setType(AssociationType $type): PartAssociation + { + $this->type = $type; + return $this; + } + + /** + * Returns a comment, which describes this association further. + * @return string|null + */ + public function getComment(): ?string + { + return $this->comment; + } + + /** + * Sets a comment, which describes this association further. + * @param string|null $comment + * @return $this + */ + public function setComment(?string $comment): PartAssociation + { + $this->comment = $comment; + return $this; + } + + /** + * Returns the part which "owns" this association, e.g. the part which is a replacement for another part. + * @return Part|null + */ + public function getOwner(): ?Part + { + return $this->owner; + } + + /** + * Sets the part which "owns" this association, e.g. the part which is a replacement for another part. + * @param Part|null $owner + * @return $this + */ + public function setOwner(?Part $owner): PartAssociation + { + $this->owner = $owner; + return $this; + } + + /** + * Returns the part which is "owned" by this association, e.g. the part which is replaced by another part. + * @return Part|null + */ + public function getOther(): ?Part + { + return $this->other; + } + + /** + * Sets the part which is "owned" by this association, e.g. the part which is replaced by another part. + * @param Part|null $other + * @return $this + */ + public function setOther(?Part $other): PartAssociation + { + $this->other = $other; + return $this; + } + + /** + * Returns the user defined association type, which is used if the type is set to OTHER. + * @return string|null + */ + public function getOtherType(): ?string + { + return $this->other_type; + } + + /** + * Sets the user defined association type, which is used if the type is set to OTHER. + * @param string|null $other_type + * @return $this + */ + public function setOtherType(?string $other_type): PartAssociation + { + $this->other_type = $other_type; + return $this; + } + + /** + * Returns the translation key for the type of this association. + * If the type is set to OTHER, then the other_type field value is used. + * @return string + */ + public function getTypeTranslationKey(): string + { + if ($this->type === AssociationType::OTHER) { + return $this->other_type ?? 'Unknown'; + } + return $this->type->getTranslationKey(); + } + +} \ No newline at end of file diff --git a/src/Entity/Parts/PartCustomState.php b/src/Entity/Parts/PartCustomState.php new file mode 100644 index 00000000..77530ff6 --- /dev/null +++ b/src/Entity/Parts/PartCustomState.php @@ -0,0 +1,129 @@ +. + */ + +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\TypeInfo\Type\NullableType; +use Symfony\Component\TypeInfo\Type\ObjectType; +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, nativeType: new NullableType(new ObjectType(self::class)))] + 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/PartLot.php b/src/Entity/Parts/PartLot.php index e552c06a..d893e6de 100644 --- a/src/Entity/Parts/PartLot.php +++ b/src/Entity/Parts/PartLot.php @@ -22,7 +22,22 @@ declare(strict_types=1); namespace App\Entity\Parts; -use App\Repository\PartLotRepository; +use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; +use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Doctrine\Orm\Filter\RangeFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +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\Validator\Constraints\Year2038BugWorkaround; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\TimestampTrait; @@ -34,8 +49,10 @@ use App\Validator\Constraints\ValidPartLot; use DateTime; use Doctrine\ORM\Mapping as ORM; use Exception; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Context\ExecutionContextInterface; /** @@ -47,9 +64,28 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ORM\Entity] #[ORM\HasLifecycleCallbacks] #[ORM\Table(name: 'part_lots')] -#[ORM\Index(name: 'part_lots_idx_instock_un_expiration_id_part', columns: ['instock_unknown', 'expiration_date', 'id_part'])] -#[ORM\Index(name: 'part_lots_idx_needs_refill', columns: ['needs_refill'])] +#[ORM\Index(columns: ['instock_unknown', 'expiration_date', 'id_part'], name: 'part_lots_idx_instock_un_expiration_id_part')] +#[ORM\Index(columns: ['needs_refill'], name: 'part_lots_idx_needs_refill')] +#[ORM\Index(columns: ['vendor_barcode'], name: 'part_lots_idx_barcode')] #[ValidPartLot] +#[UniqueEntity(['user_barcode'], message: 'validator.part_lot.vendor_barcode_must_be_unique')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@parts.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['part_lot:read', 'part_lot:read:standalone', 'api:basic:read', 'pricedetail:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['part_lot:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["description", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(BooleanFilter::class, properties: ['instock_unknown', 'needs_refill'])] +#[ApiFilter(RangeFilter::class, properties: ['amount'])] +#[ApiFilter(OrderFilter::class, properties: ['description', 'comment', 'addedDate', 'lastModified'])] class PartLot extends AbstractDBElement implements TimeStampableInterface, NamedElementInterface { use TimestampTrait; @@ -57,53 +93,54 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * @var string A short description about this lot, shown in table */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::TEXT)] protected string $description = ''; /** * @var string a comment stored with this lot */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::TEXT)] protected string $comment = ''; /** - * @var \DateTimeInterface|null Set a time until when the lot must be used. + * @var \DateTimeImmutable|null Set a time until when the lot must be used. * Set to null, if the lot can be used indefinitely. */ - #[Groups(['extended', 'full', 'import'])] - #[ORM\Column(type: Types::DATETIME_MUTABLE, name: 'expiration_date', nullable: true)] - protected ?\DateTimeInterface $expiration_date = null; + #[Groups(['extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] + #[ORM\Column(name: 'expiration_date', type: Types::DATETIME_IMMUTABLE, nullable: true)] + #[Year2038BugWorkaround] + protected ?\DateTimeImmutable $expiration_date = null; /** - * @var Storelocation|null The storelocation of this lot + * @var StorageLocation|null The storelocation of this lot */ - #[Groups(['simple', 'extended', 'full', 'import'])] - #[ORM\ManyToOne(targetEntity: Storelocation::class, fetch: 'EAGER')] + #[Groups(['simple', 'extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] + #[ORM\ManyToOne(targetEntity: StorageLocation::class, fetch: 'EAGER')] #[ORM\JoinColumn(name: 'id_store_location')] - #[Selectable()] - protected ?Storelocation $storage_location = null; + #[Selectable] + protected ?StorageLocation $storage_location = null; /** * @var bool If this is set to true, the instock amount is marked as not known */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $instock_unknown = false; /** - * @var float For continuous sizes (length, volume, etc.) the instock is saved here. + * @var float The amount of parts in this lot. For integer-quantities this value is rounded to the next integer. */ #[Assert\PositiveOrZero] - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::FLOAT)] protected float $amount = 0.0; /** * @var bool determines if this lot was manually marked for refilling */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $needs_refill = false; @@ -113,6 +150,8 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named #[Assert\NotNull] #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'partLots')] #[ORM\JoinColumn(name: 'id_part', nullable: false, onDelete: 'CASCADE')] + #[Groups(['part_lot:read:standalone', 'part_lot:write'])] + #[ApiProperty(writableLink: false)] protected ?Part $part = null; /** @@ -120,8 +159,18 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named */ #[ORM\ManyToOne(targetEntity: User::class)] #[ORM\JoinColumn(name: 'id_owner', onDelete: 'SET NULL')] + #[Groups(['part_lot:read', 'part_lot:write'])] + #[ApiProperty(writableLink: false)] protected ?User $owner = null; + /** + * @var string|null The content of the barcode of this part lot (e.g. a barcode on the package put by the vendor) + */ + #[ORM\Column(name: "vendor_barcode", type: Types::STRING, nullable: true)] + #[Groups(['part_lot:read', 'part_lot:write'])] + #[Length(max: 255)] + protected ?string $user_barcode = null; + public function __clone() { if ($this->id) { @@ -136,7 +185,6 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named * * @return bool|null True, if the part lot is expired. Returns null, if no expiration date was set. * - * @throws Exception If an error with the DateTime occurs */ public function isExpired(): ?bool { @@ -145,7 +193,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named } //Check if the expiration date is bigger then current time - return $this->expiration_date < new DateTime('now'); + return $this->expiration_date < new \DateTimeImmutable('now'); } /** @@ -187,7 +235,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Gets the expiration date for the part lot. Returns null, if no expiration date was set. */ - public function getExpirationDate(): ?\DateTimeInterface + public function getExpirationDate(): ?\DateTimeImmutable { return $this->expiration_date; } @@ -197,7 +245,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named * * */ - public function setExpirationDate(?\DateTimeInterface $expiration_date): self + public function setExpirationDate(?\DateTimeImmutable $expiration_date): self { $this->expiration_date = $expiration_date; @@ -207,9 +255,9 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Gets the storage location, where this part lot is stored. * - * @return Storelocation|null The store location where this part is stored + * @return StorageLocation|null The store location where this part is stored */ - public function getStorageLocation(): ?Storelocation + public function getStorageLocation(): ?StorageLocation { return $this->storage_location; } @@ -217,7 +265,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Sets the storage location, where this part lot is stored. */ - public function setStorageLocation(?Storelocation $storage_location): self + public function setStorageLocation(?StorageLocation $storage_location): self { $this->storage_location = $storage_location; @@ -322,6 +370,29 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named return $this->description; } + /** + * The content of the barcode of this part lot (e.g. a barcode on the package put by the vendor), or + * null if no barcode is set. + * @return string|null + */ + public function getUserBarcode(): ?string + { + return $this->user_barcode; + } + + /** + * Set the content of the barcode of this part lot (e.g. a barcode on the package put by the vendor). + * @param string|null $user_barcode + * @return $this + */ + public function setUserBarcode(?string $user_barcode): PartLot + { + $this->user_barcode = $user_barcode; + return $this; + } + + + #[Assert\Callback] public function validate(ExecutionContextInterface $context, $payload): void { diff --git a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php index 648cf2a5..2cee7f1a 100644 --- a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php +++ b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php @@ -23,11 +23,14 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; use App\Entity\Parts\InfoProviderReference; +use App\Entity\Parts\PartCustomState; use Doctrine\DBAL\Types\Types; use App\Entity\Parts\Part; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Constraints\Length; +use App\Validator\Constraints\UniquePartIpnConstraint; /** * Advanced properties of a part, not related to a more specific group. @@ -37,22 +40,22 @@ trait AdvancedPropertyTrait /** * @var bool Determines if this part entry needs review (for example, because it is work in progress) */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $needs_review = false; /** - * @var string a comma separated list of tags, associated with the part + * @var string A comma separated list of tags, associated with the part */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $tags = ''; /** - * @var float|null how much a single part unit weighs in grams + * @var float|null How much a single part unit weighs in grams */ #[Assert\PositiveOrZero] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $mass = null; @@ -60,16 +63,27 @@ trait AdvancedPropertyTrait * @var string|null The internal part number of the part */ #[Assert\Length(max: 100)] - #[Groups(['extended', 'full', 'import'])] - #[ORM\Column(type: Types::STRING, length: 100, nullable: true, unique: true)] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] + #[ORM\Column(type: Types::STRING, length: 100, unique: true, nullable: true)] + #[Length(max: 100)] + #[UniquePartIpnConstraint] protected ?string $ipn = null; /** * @var InfoProviderReference The reference to the info provider, that provided the information about this part */ #[ORM\Embedded(class: InfoProviderReference::class, columnPrefix: 'provider_reference_')] + #[Groups(['full', 'part:read'])] protected InfoProviderReference $providerReference; + /** + * @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. */ @@ -177,7 +191,24 @@ trait AdvancedPropertyTrait return $this; } + /** + * Gets the custom part state for the part + * Returns null if no specific part state is set. + */ + public function getPartCustomState(): ?PartCustomState + { + return $this->partCustomState; + } + /** + * Sets the custom part state. + * + * @return $this + */ + public function setPartCustomState(?PartCustomState $partCustomState): self + { + $this->partCustomState = $partCustomState; - + return $this; + } } diff --git a/src/Entity/Parts/PartTraits/AssociationTrait.php b/src/Entity/Parts/PartTraits/AssociationTrait.php new file mode 100644 index 00000000..bb80fc5a --- /dev/null +++ b/src/Entity/Parts/PartTraits/AssociationTrait.php @@ -0,0 +1,110 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity\Parts\PartTraits; + +use App\Entity\Parts\PartAssociation; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints\Valid; +use Doctrine\ORM\Mapping as ORM; + +trait AssociationTrait +{ + /** + * @var Collection All associations where this part is the owner + */ + #[Valid] + #[ORM\OneToMany(mappedBy: 'owner', targetEntity: PartAssociation::class, + cascade: ['persist', 'remove'], orphanRemoval: true)] + #[Groups(['part:read', 'part:write', 'full'])] + protected Collection $associated_parts_as_owner; + + /** + * @var Collection All associations where this part is the owned/other part + */ + #[Valid] + #[ORM\OneToMany(mappedBy: 'other', targetEntity: PartAssociation::class, + cascade: ['persist', 'remove'], orphanRemoval: true)] + #[Groups(['part:read'])] + protected Collection $associated_parts_as_other; + + /** + * Returns all associations where this part is the owner. + * @return Collection + */ + public function getAssociatedPartsAsOwner(): Collection + { + return $this->associated_parts_as_owner; + } + + /** + * Add a new association where this part is the owner. + * @param PartAssociation $association + * @return $this + */ + public function addAssociatedPartsAsOwner(PartAssociation $association): self + { + //Ensure that the association is really owned by this part + $association->setOwner($this); + + $this->associated_parts_as_owner->add($association); + return $this; + } + + /** + * Remove an association where this part is the owner. + * @param PartAssociation $association + * @return $this + */ + public function removeAssociatedPartsAsOwner(PartAssociation $association): self + { + $this->associated_parts_as_owner->removeElement($association); + return $this; + } + + /** + * Returns all associations where this part is the owned/other part. + * If you want to modify the association, do it on the owning part + * @return Collection + */ + public function getAssociatedPartsAsOther(): Collection + { + return $this->associated_parts_as_other; + } + + /** + * Returns all associations where this part is the owned or other part. + * @return Collection + */ + public function getAssociatedPartsAll(): Collection + { + return new ArrayCollection( + array_merge( + $this->associated_parts_as_owner->toArray(), + $this->associated_parts_as_other->toArray() + ) + ); + } +} \ No newline at end of file diff --git a/src/Entity/Parts/PartTraits/BasicPropertyTrait.php b/src/Entity/Parts/PartTraits/BasicPropertyTrait.php index b0f593d6..7e483ed2 100644 --- a/src/Entity/Parts/PartTraits/BasicPropertyTrait.php +++ b/src/Entity/Parts/PartTraits/BasicPropertyTrait.php @@ -35,14 +35,14 @@ trait BasicPropertyTrait /** * @var string A text describing what this part does */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $description = ''; /** * @var string A comment/note related to this part */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $comment = ''; @@ -55,7 +55,7 @@ trait BasicPropertyTrait /** * @var bool true, if the part is marked as favorite */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $favorite = false; @@ -64,8 +64,8 @@ trait BasicPropertyTrait * Every part must have a category. */ #[Assert\NotNull(message: 'validator.select_valid_category')] - #[Selectable()] - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Selectable] + #[Groups(['simple', 'extended', 'full', 'import', "part:read", "part:write"])] #[ORM\ManyToOne(targetEntity: Category::class)] #[ORM\JoinColumn(name: 'id_category', nullable: false)] protected ?Category $category = null; @@ -73,10 +73,10 @@ trait BasicPropertyTrait /** * @var Footprint|null The footprint of this part (e.g. DIP8) */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\ManyToOne(targetEntity: Footprint::class)] #[ORM\JoinColumn(name: 'id_footprint')] - #[Selectable()] + #[Selectable] protected ?Footprint $footprint = null; /** diff --git a/src/Entity/Parts/PartTraits/EDATrait.php b/src/Entity/Parts/PartTraits/EDATrait.php new file mode 100644 index 00000000..313552e7 --- /dev/null +++ b/src/Entity/Parts/PartTraits/EDATrait.php @@ -0,0 +1,53 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity\Parts\PartTraits; + +use App\Entity\EDA\EDAPartInfo; +use Doctrine\ORM\Mapping\Embedded; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints\Valid; + +trait EDATrait +{ + #[Valid] + #[Embedded(class: EDAPartInfo::class)] + #[Groups(['full', 'part:read', 'part:write', 'import'])] + protected EDAPartInfo $eda_info; + + public function getEdaInfo(): EDAPartInfo + { + return $this->eda_info; + } + + public function setEdaInfo(?EDAPartInfo $eda_info): self + { + if ($eda_info !== null) { + //Do a clone, to ensure that the property is updated in the database + $eda_info = clone $eda_info; + } + + $this->eda_info = $eda_info; + return $this; + } +} \ No newline at end of file diff --git a/src/Entity/Parts/PartTraits/InstockTrait.php b/src/Entity/Parts/PartTraits/InstockTrait.php index 068173a7..08b070f3 100644 --- a/src/Entity/Parts/PartTraits/InstockTrait.php +++ b/src/Entity/Parts/PartTraits/InstockTrait.php @@ -22,12 +22,14 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; +use Doctrine\Common\Collections\Criteria; use Doctrine\DBAL\Types\Types; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\PartLot; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\SerializedName; use Symfony\Component\Validator\Constraints as Assert; /** @@ -36,12 +38,12 @@ use Symfony\Component\Validator\Constraints as Assert; trait InstockTrait { /** - * @var Collection|PartLot[] A list of part lots where this part is stored + * @var Collection A list of part lots where this part is stored */ #[Assert\Valid] - #[Groups(['extended', 'full', 'import'])] - #[ORM\OneToMany(targetEntity: PartLot::class, mappedBy: 'part', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['amount' => 'DESC'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] + #[ORM\OneToMany(mappedBy: 'part', targetEntity: PartLot::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['amount' => Criteria::DESC])] protected Collection $partLots; /** @@ -49,14 +51,14 @@ trait InstockTrait * Given in the partUnit. */ #[Assert\PositiveOrZero] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::FLOAT)] protected float $minamount = 0; /** * @var ?MeasurementUnit the unit in which the part's amount is measured */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\ManyToOne(targetEntity: MeasurementUnit::class)] #[ORM\JoinColumn(name: 'id_part_unit')] protected ?MeasurementUnit $partUnit = null; @@ -181,6 +183,8 @@ trait InstockTrait * * @return float The amount of parts given in partUnit */ + #[Groups(['simple', 'extended', 'full', 'part:read'])] + #[SerializedName('total_instock')] public function getAmountSum(): float { //TODO: Find a method to do this natively in SQL, the current method could be a bit slow diff --git a/src/Entity/Parts/PartTraits/ManufacturerTrait.php b/src/Entity/Parts/PartTraits/ManufacturerTrait.php index 97ef246b..911a0806 100644 --- a/src/Entity/Parts/PartTraits/ManufacturerTrait.php +++ b/src/Entity/Parts/PartTraits/ManufacturerTrait.php @@ -30,6 +30,7 @@ use App\Validator\Constraints\Selectable; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Constraints\Length; /** * In this trait all manufacturer related properties of a part are collected (like MPN, manufacturer URL). @@ -39,31 +40,32 @@ trait ManufacturerTrait /** * @var Manufacturer|null The manufacturer of this part */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\ManyToOne(targetEntity: Manufacturer::class)] #[ORM\JoinColumn(name: 'id_manufacturer')] - #[Selectable()] + #[Selectable] protected ?Manufacturer $manufacturer = null; /** - * @var string the url to the part on the manufacturer's homepage + * @var string The url to the part on the manufacturer's homepage */ - #[Assert\Url] - #[Groups(['full', 'import'])] + #[Assert\Url(requireTld: false)] + #[Groups(['full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $manufacturer_product_url = ''; /** * @var string The product number used by the manufacturer. If this is set to "", the name field is used. */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::STRING)] + #[Length(max: 255)] protected string $manufacturer_product_number = ''; /** * @var ManufacturingStatus|null The production status of this part. Can be one of the specified ones. */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::STRING, length: 255, nullable: true, enumType: ManufacturingStatus::class)] protected ?ManufacturingStatus $manufacturing_status = ManufacturingStatus::NOT_SET; diff --git a/src/Entity/Parts/PartTraits/OrderTrait.php b/src/Entity/Parts/PartTraits/OrderTrait.php index a442298c..2c142016 100644 --- a/src/Entity/Parts/PartTraits/OrderTrait.php +++ b/src/Entity/Parts/PartTraits/OrderTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; +use Doctrine\Common\Collections\Criteria; use Doctrine\DBAL\Types\Types; use App\Entity\PriceInformations\Orderdetail; use Symfony\Component\Serializer\Annotation\Groups; @@ -36,12 +37,12 @@ use Doctrine\ORM\Mapping as ORM; trait OrderTrait { /** - * @var Collection the details about how and where you can order this part + * @var Collection The details about how and where you can order this part */ #[Assert\Valid] - #[Groups(['extended', 'full', 'import'])] - #[ORM\OneToMany(targetEntity: Orderdetail::class, mappedBy: 'part', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['supplierpartnr' => 'ASC'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] + #[ORM\OneToMany(mappedBy: 'part', targetEntity: Orderdetail::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['supplierpartnr' => Criteria::ASC])] protected Collection $orderdetails; /** diff --git a/src/Entity/Parts/PartTraits/ProjectTrait.php b/src/Entity/Parts/PartTraits/ProjectTrait.php index 51208b6a..7e1962d3 100644 --- a/src/Entity/Parts/PartTraits/ProjectTrait.php +++ b/src/Entity/Parts/PartTraits/ProjectTrait.php @@ -6,25 +6,22 @@ namespace App\Entity\Parts\PartTraits; use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; -use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; trait ProjectTrait { /** * @var Collection $project_bom_entries */ - /** - * @var Collection $project_bom_entries - */ - #[ORM\OneToMany(targetEntity: ProjectBOMEntry::class, mappedBy: 'part', cascade: ['remove'], orphanRemoval: true)] + #[ORM\OneToMany(targetEntity: ProjectBOMEntry::class, mappedBy: 'part')] protected Collection $project_bom_entries; /** * @var Project|null If a project is set here, then this part is special and represents the builds of a project. */ - #[ORM\OneToOne(targetEntity: Project::class, inversedBy: 'build_part')] + #[ORM\OneToOne(inversedBy: 'build_part', targetEntity: Project::class)] #[ORM\JoinColumn] protected ?Project $built_project = null; @@ -42,6 +39,7 @@ trait ProjectTrait * Checks whether this part represents the builds of a project * @return bool True if it represents the builds, false if not */ + #[Groups(['part:read'])] public function isProjectBuildPart(): bool { return $this->built_project !== null; diff --git a/src/Entity/Parts/Storelocation.php b/src/Entity/Parts/StorageLocation.php similarity index 59% rename from src/Entity/Parts/Storelocation.php rename to src/Entity/Parts/StorageLocation.php index f58f807d..ca76df59 100644 --- a/src/Entity/Parts/Storelocation.php +++ b/src/Entity/Parts/StorageLocation.php @@ -22,70 +22,123 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; +use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; use App\Repository\Parts\StorelocationRepository; use Doctrine\DBAL\Types\Types; use Doctrine\Common\Collections\ArrayCollection; -use App\Entity\Attachments\StorelocationAttachment; +use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Base\AbstractPartsContainingDBElement; use App\Entity\Base\AbstractStructuralDBElement; -use App\Entity\Parameters\StorelocationParameter; +use App\Entity\Parameters\StorageLocationParameter; use App\Entity\UserSystem\User; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\TypeInfo\Type\NullableType; +use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\Validator\Constraints as Assert; /** * This entity represents a storage location, where parts can be stored. - * @extends AbstractPartsContainingDBElement + * @extends AbstractPartsContainingDBElement */ #[ORM\Entity(repositoryClass: StorelocationRepository::class)] #[ORM\Table('`storelocations`')] -#[ORM\Index(name: 'location_idx_name', columns: ['name'])] -#[ORM\Index(name: 'location_idx_parent_name', columns: ['parent_id', 'name'])] -class Storelocation extends AbstractPartsContainingDBElement +#[ORM\Index(columns: ['name'], name: 'location_idx_name')] +#[ORM\Index(columns: ['parent_id', 'name'], name: 'location_idx_parent_name')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@storelocations.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['location:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['location:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/storage_locations/{id}/children.{_format}', + operations: [ + new GetCollection( + openapi: new Operation(summary: 'Retrieves the children elements of a storage location.'), + security: 'is_granted("@storelocations.read")' + ) + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: Manufacturer::class) + ], + normalizationContext: ['groups' => ['location:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] +class StorageLocation extends AbstractPartsContainingDBElement { - #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['location:read', 'location:write'])] + #[ApiProperty(readableLink: false, writableLink: false, nativeType: new NullableType(new ObjectType(self::class)))] protected ?AbstractStructuralDBElement $parent = null; + #[Groups(['location:read', 'location:write'])] + protected string $comment = ''; + /** * @var MeasurementUnit|null The measurement unit, which parts can be stored in here */ #[ORM\ManyToOne(targetEntity: MeasurementUnit::class)] #[ORM\JoinColumn(name: 'storage_type_id')] + #[Groups(['location:read', 'location:write'])] protected ?MeasurementUnit $storage_type = null; - /** @var Collection + /** @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: StorelocationParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: StorageLocationParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] + #[Groups(['location:read', 'location:write'])] protected Collection $parameters; /** - * @var bool + * @var bool When this attribute is set, it is not possible to add additional parts or increase the instock of existing parts. */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'location:read', 'location:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $is_full = false; /** - * @var bool + * @var bool When this property is set, only one part (but many instock) is allowed to be stored in this store location. */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'location:read', 'location:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $only_single_part = false; /** - * @var bool + * @var bool When this property is set, it is only possible to increase the instock of parts, that are already stored here. */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'location:read', 'location:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $limit_to_existing_parts = false; @@ -95,25 +148,35 @@ class Storelocation extends AbstractPartsContainingDBElement #[Assert\Expression('this.getOwner() == null or this.getOwner().isAnonymousUser() === false', message: 'validator.part_lot.owner_must_not_be_anonymous')] #[ORM\ManyToOne(targetEntity: User::class)] #[ORM\JoinColumn(name: 'id_owner', onDelete: 'SET NULL')] + #[Groups(['location:read', 'location:write'])] protected ?User $owner = null; /** * @var bool If this is set to true, only parts lots, which are owned by the same user as the store location are allowed to be stored here. */ #[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])] + #[Groups(['location:read', 'location:write'])] protected bool $part_owner_must_match = false; /** - * @var Collection + * @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: StorelocationAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: StorageLocationAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[Groups(['location:read', 'location:write'])] protected Collection $attachments; - #[ORM\ManyToOne(targetEntity: StorelocationAttachment::class)] + #[ORM\ManyToOne(targetEntity: StorageLocationAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['location:read', 'location:write'])] protected ?Attachment $master_picture_attachment = null; + #[Groups(['location:read'])] + protected ?\DateTimeImmutable $addedDate = null; + #[Groups(['location:read'])] + protected ?\DateTimeImmutable $lastModified = null; + + /******************************************************************************** * * Getters @@ -186,7 +249,7 @@ class Storelocation extends AbstractPartsContainingDBElement /** * Sets the owner of this storage location */ - public function setOwner(?User $owner): Storelocation + public function setOwner(?User $owner): StorageLocation { $this->owner = $owner; return $this; @@ -203,7 +266,7 @@ class Storelocation extends AbstractPartsContainingDBElement /** * If this is set to true, only parts lots, which are owned by the same user as the store location are allowed to be stored here. */ - public function setPartOwnerMustMatch(bool $part_owner_must_match): Storelocation + public function setPartOwnerMustMatch(bool $part_owner_must_match): StorageLocation { $this->part_owner_must_match = $part_owner_must_match; return $this; diff --git a/src/Entity/Parts/Supplier.php b/src/Entity/Parts/Supplier.php index 4dca8f36..a073e9e4 100644 --- a/src/Entity/Parts/Supplier.php +++ b/src/Entity/Parts/Supplier.php @@ -22,8 +22,23 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; +use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\Parts\SupplierRepository; use App\Entity\PriceInformations\Orderdetail; use Doctrine\Common\Collections\ArrayCollection; @@ -38,6 +53,8 @@ use Brick\Math\BigDecimal; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\TypeInfo\Type\NullableType; +use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\Validator\Constraints as Assert; /** @@ -47,25 +64,50 @@ use Symfony\Component\Validator\Constraints as Assert; */ #[ORM\Entity(repositoryClass: SupplierRepository::class)] #[ORM\Table('`suppliers`')] -#[ORM\Index(name: 'supplier_idx_name', columns: ['name'])] -#[ORM\Index(name: 'supplier_idx_parent_name', columns: ['parent_id', 'name'])] +#[ORM\Index(columns: ['name'], name: 'supplier_idx_name')] +#[ORM\Index(columns: ['parent_id', 'name'], name: 'supplier_idx_parent_name')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@suppliers.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['supplier:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['supplier:write', 'company:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/suppliers/{id}/children.{_format}', + operations: [new GetCollection( + openapi: new Operation(summary: 'Retrieves the children elements of a supplier.'), + security: 'is_granted("@manufacturers.read")' + )], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: Supplier::class) + ], + normalizationContext: ['groups' => ['supplier:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class Supplier extends AbstractCompany { - #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['supplier:read', 'supplier:write'])] + #[ApiProperty(readableLink: false, writableLink: false, nativeType: new NullableType(new ObjectType(self::class)))] protected ?AbstractStructuralDBElement $parent = null; /** - * @var Collection|Orderdetail[] + * @var Collection */ - /** - * @var Collection|Orderdetail[] - */ - #[ORM\OneToMany(targetEntity: Orderdetail::class, mappedBy: 'supplier')] + #[ORM\OneToMany(mappedBy: 'supplier', targetEntity: Orderdetail::class)] protected Collection $orderdetails; /** @@ -74,34 +116,40 @@ class Supplier extends AbstractCompany */ #[ORM\ManyToOne(targetEntity: Currency::class)] #[ORM\JoinColumn(name: 'default_currency_id')] - #[Selectable()] + #[Selectable] protected ?Currency $default_currency = null; /** - * @var BigDecimal|null the shipping costs that have to be paid, when ordering via this supplier + * @var BigDecimal|null The shipping costs that have to be paid, when ordering via this supplier */ #[Groups(['extended', 'full', 'import'])] - #[ORM\Column(name: 'shipping_costs', nullable: true, type: 'big_decimal', precision: 11, scale: 5)] - #[BigDecimalPositiveOrZero()] + #[ORM\Column(name: 'shipping_costs', type: 'big_decimal', precision: 11, scale: 5, nullable: true)] + #[BigDecimalPositiveOrZero] protected ?BigDecimal $shipping_costs = null; /** * @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: SupplierAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: SupplierAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => Criteria::ASC])] + #[Groups(['supplier:read', 'supplier:write'])] + #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: SupplierAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['supplier:read', 'supplier:write'])] + #[ApiProperty(readableLink: false, writableLink: true)] protected ?Attachment $master_picture_attachment = null; /** @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: SupplierParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: SupplierParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] + #[Groups(['supplier:read', 'supplier:write'])] + #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $parameters; /** diff --git a/src/Entity/PriceInformations/Currency.php b/src/Entity/PriceInformations/Currency.php index 1aec8d8a..4bf9df4e 100644 --- a/src/Entity/PriceInformations/Currency.php +++ b/src/Entity/PriceInformations/Currency.php @@ -22,8 +22,23 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use Doctrine\Common\Collections\Criteria; +use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\CurrencyRepository; use Doctrine\DBAL\Types\Types; use App\Entity\Attachments\CurrencyAttachment; @@ -37,6 +52,8 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\TypeInfo\Type\NullableType; +use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\Validator\Constraints as Assert; /** @@ -47,8 +64,36 @@ use Symfony\Component\Validator\Constraints as Assert; #[UniqueEntity('iso_code')] #[ORM\Entity(repositoryClass: CurrencyRepository::class)] #[ORM\Table(name: 'currencies')] -#[ORM\Index(name: 'currency_idx_name', columns: ['name'])] -#[ORM\Index(name: 'currency_idx_parent_name', columns: ['parent_id', 'name'])] +#[ORM\Index(columns: ['name'], name: 'currency_idx_name')] +#[ORM\Index(columns: ['parent_id', 'name'], name: 'currency_idx_parent_name')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@currencies.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['currency:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['currency:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/currencies/{id}/children.{_format}', + operations: [ + new GetCollection( + openapi: new Operation(summary: 'Retrieves the children elements of a currency.'), + security: 'is_granted("@currencies.read")' + ) + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: Currency::class) + ], + normalizationContext: ['groups' => ['currency:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment", "iso_code"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class Currency extends AbstractStructuralDBElement { final public const PRICE_SCALE = 5; @@ -58,50 +103,66 @@ class Currency extends AbstractStructuralDBElement * (how many base units the current currency is worth) */ #[ORM\Column(type: 'big_decimal', precision: 11, scale: 5, nullable: true)] - #[BigDecimalPositive()] + #[BigDecimalPositive] + #[Groups(['currency:read', 'currency:write', 'simple', 'extended', 'full', 'import'])] + #[ApiProperty(readableLink: false, writableLink: false)] protected ?BigDecimal $exchange_rate = null; + #[Groups(['currency:read', 'currency:write'])] + protected string $comment = ""; + /** * @var string the 3-letter ISO code of the currency */ #[Assert\Currency] #[Assert\NotBlank] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'currency:read', 'currency:write'])] #[ORM\Column(type: Types::STRING)] protected string $iso_code = ""; - #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent', cascade: ['persist'])] - #[ORM\OrderBy(['name' => 'ASC'])] + #[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(['currency:read', 'currency:write'])] + #[ApiProperty(readableLink: false, writableLink: false, nativeType: new NullableType(new ObjectType(self::class)))] protected ?AbstractStructuralDBElement $parent = null; /** * @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: CurrencyAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: CurrencyAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => Criteria::ASC])] + #[Groups(['currency:read', 'currency:write'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: CurrencyAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['currency:read', 'currency:write'])] protected ?Attachment $master_picture_attachment = null; /** @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: CurrencyParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: CurrencyParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] + #[Groups(['currency:read', 'currency:write'])] protected Collection $parameters; /** @var Collection */ - #[ORM\OneToMany(targetEntity: Pricedetail::class, mappedBy: 'currency')] + #[ORM\OneToMany(mappedBy: 'currency', targetEntity: Pricedetail::class)] protected Collection $pricedetails; + #[Groups(['currency:read'])] + protected ?\DateTimeImmutable $addedDate = null; + #[Groups(['currency:read'])] + protected ?\DateTimeImmutable $lastModified = null; + + public function __construct() { $this->children = new ArrayCollection(); @@ -136,6 +197,7 @@ class Currency extends AbstractStructuralDBElement /** * Returns the inverse exchange rate (how many of the current currency the base unit is worth). */ + #[Groups(['currency:read'])] public function getInverseExchangeRate(): ?BigDecimal { $tmp = $this->getExchangeRate(); diff --git a/src/Entity/PriceInformations/Orderdetail.php b/src/Entity/PriceInformations/Orderdetail.php index d61eeb68..8ed76a46 100644 --- a/src/Entity/PriceInformations/Orderdetail.php +++ b/src/Entity/PriceInformations/Orderdetail.php @@ -23,6 +23,22 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use Doctrine\Common\Collections\Criteria; +use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; +use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +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\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\TimestampTrait; @@ -30,13 +46,14 @@ use App\Entity\Contracts\NamedElementInterface; use App\Entity\Contracts\TimeStampableInterface; use App\Entity\Parts\Part; use App\Entity\Parts\Supplier; -use DateTime; +use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Constraints\Length; /** * Class Orderdetail. @@ -45,52 +62,87 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Entity] #[ORM\HasLifecycleCallbacks] #[ORM\Table('`orderdetails`')] -#[ORM\Index(name: 'orderdetails_supplier_part_nr', columns: ['supplierpartnr'])] +#[ORM\Index(columns: ['supplierpartnr'], name: 'orderdetails_supplier_part_nr')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@parts.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['orderdetail:read', 'orderdetail:read:standalone', 'api:basic:read', 'pricedetail:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['orderdetail:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/parts/{id}/orderdetails.{_format}', + operations: [ + new GetCollection( + openapi: new Operation(summary: 'Retrieves the orderdetails of a part.'), + security: 'is_granted("@parts.read")' + ) + ], + uriVariables: [ + 'id' => new Link(toProperty: 'part', fromClass: Part::class) + ], + normalizationContext: ['groups' => ['orderdetail:read', 'pricedetail:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["supplierpartnr", "supplier_product_url"])] +#[ApiFilter(BooleanFilter::class, properties: ["obsolete"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['supplierpartnr', 'id', 'addedDate', 'lastModified'])] class Orderdetail extends AbstractDBElement implements TimeStampableInterface, NamedElementInterface { use TimestampTrait; + /** + * @var Collection + */ #[Assert\Valid] - #[Groups(['extended', 'full', 'import'])] - #[ORM\OneToMany(targetEntity: Pricedetail::class, mappedBy: 'orderdetail', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['min_discount_quantity' => 'ASC'])] + #[Groups(['extended', 'full', 'import', 'orderdetail:read', 'orderdetail:write'])] + #[ORM\OneToMany(mappedBy: 'orderdetail', targetEntity: Pricedetail::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['min_discount_quantity' => Criteria::ASC])] protected Collection $pricedetails; /** - * @var string + * @var string The order number of the part at the supplier */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\Column(type: Types::STRING)] + #[Length(max: 255)] protected string $supplierpartnr = ''; /** - * @var bool + * @var bool True if this part is obsolete/not available anymore at the supplier */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $obsolete = false; /** - * @var string + * @var string The URL to the product on the supplier's website */ - #[Assert\Url] - #[Groups(['full', 'import'])] + #[Assert\Url(requireTld: false)] + #[Groups(['full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\Column(type: Types::TEXT)] protected string $supplier_product_url = ''; /** - * @var Part|null + * @var Part|null The part with which this orderdetail is associated */ #[Assert\NotNull] #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'orderdetails')] + #[Groups(['orderdetail:read:standalone', 'orderdetail:write'])] #[ORM\JoinColumn(name: 'part_id', nullable: false, onDelete: 'CASCADE')] protected ?Part $part = null; /** - * @var Supplier|null + * @var Supplier|null The supplier of this orderdetail */ #[Assert\NotNull(message: 'validator.orderdetail.supplier_must_not_be_null')] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\ManyToOne(targetEntity: Supplier::class, inversedBy: 'orderdetails')] #[ORM\JoinColumn(name: 'id_supplier')] protected ?Supplier $supplier = null; @@ -121,9 +173,9 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N #[ORM\PreUpdate] public function updateTimestamps(): void { - $this->lastModified = new DateTime('now'); + $this->lastModified = new DateTimeImmutable('now'); if (!$this->addedDate instanceof \DateTimeInterface) { - $this->addedDate = new DateTime('now'); + $this->addedDate = new DateTimeImmutable('now'); } if ($this->part instanceof Part) { @@ -181,6 +233,11 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N return $this->obsolete; } + public function isObsolete(): bool + { + return $this->getObsolete(); + } + /** * Get the link to the website of the article on the supplier's website. * diff --git a/src/Entity/PriceInformations/Pricedetail.php b/src/Entity/PriceInformations/Pricedetail.php index 26afbb50..86a7bcd5 100644 --- a/src/Entity/PriceInformations/Pricedetail.php +++ b/src/Entity/PriceInformations/Pricedetail.php @@ -22,6 +22,14 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\TimestampTrait; @@ -30,10 +38,11 @@ use App\Validator\Constraints\BigDecimal\BigDecimalPositive; use App\Validator\Constraints\Selectable; use Brick\Math\BigDecimal; use Brick\Math\RoundingMode; -use DateTime; +use DateTimeImmutable; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Validator\Constraints as Assert; /** @@ -43,8 +52,20 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Entity] #[ORM\HasLifecycleCallbacks] #[ORM\Table('`pricedetails`')] -#[ORM\Index(name: 'pricedetails_idx_min_discount', columns: ['min_discount_quantity'])] -#[ORM\Index(name: 'pricedetails_idx_min_discount_price_qty', columns: ['min_discount_quantity', 'price_related_quantity'])] +#[ORM\Index(columns: ['min_discount_quantity'], name: 'pricedetails_idx_min_discount')] +#[ORM\Index(columns: ['min_discount_quantity', 'price_related_quantity'], name: 'pricedetails_idx_min_discount_price_qty')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@parts.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['pricedetail:read', 'pricedetail:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['pricedetail:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiFilter(PropertyFilter::class)] class Pricedetail extends AbstractDBElement implements TimeStampableInterface { use TimestampTrait; @@ -54,34 +75,34 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface /** * @var BigDecimal The price related to the detail. (Given in the selected currency) */ - #[Groups(['extended', 'full'])] + #[Groups(['extended', 'full', 'import', 'pricedetail:read', 'pricedetail:write'])] #[ORM\Column(type: 'big_decimal', precision: 11, scale: 5)] - #[BigDecimalPositive()] + #[BigDecimalPositive] protected BigDecimal $price; /** * @var ?Currency The currency used for the current price information. * If this is null, the global base unit is assumed */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'pricedetail:read', 'pricedetail:write'])] #[ORM\ManyToOne(targetEntity: Currency::class, inversedBy: 'pricedetails')] #[ORM\JoinColumn(name: 'id_currency')] - #[Selectable()] + #[Selectable] protected ?Currency $currency = null; /** - * @var float + * @var float The amount/quantity for which the price is for (in part unit) */ #[Assert\Positive] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'pricedetail:read', 'pricedetail:write'])] #[ORM\Column(type: Types::FLOAT)] protected float $price_related_quantity = 1.0; /** - * @var float + * @var float The minimum amount/quantity, which is needed to get this discount (in part unit) */ #[Assert\Positive] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'pricedetail:read', 'pricedetail:write'])] #[ORM\Column(type: Types::FLOAT)] protected float $min_discount_quantity = 1.0; @@ -97,6 +118,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface #[Assert\NotNull] #[ORM\ManyToOne(targetEntity: Orderdetail::class, inversedBy: 'pricedetails')] #[ORM\JoinColumn(name: 'orderdetails_id', nullable: false, onDelete: 'CASCADE')] + #[Groups(['pricedetail:read:standalone', 'pricedetail:write'])] protected ?Orderdetail $orderdetail = null; public function __construct() @@ -119,9 +141,9 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface #[ORM\PreUpdate] public function updateTimestamps(): void { - $this->lastModified = new DateTime('now'); + $this->lastModified = new DateTimeImmutable('now'); if (!$this->addedDate instanceof \DateTimeInterface) { - $this->addedDate = new DateTime('now'); + $this->addedDate = new DateTimeImmutable('now'); } if ($this->orderdetail instanceof Orderdetail) { @@ -167,6 +189,8 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface * * @return BigDecimal the price as a bcmath string */ + #[Groups(['pricedetail:read'])] + #[SerializedName('price_per_unit')] public function getPricePerUnit(float|string|BigDecimal $multiplier = 1.0): BigDecimal { $tmp = BigDecimal::of($multiplier); @@ -228,6 +252,18 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface return $this->currency; } + /** + * Returns the ISO code of the currency associated with this price information, or null if no currency is selected. + * Then the global base currency should be assumed. + * @return string|null + */ + #[Groups(['pricedetail:read'])] + #[SerializedName('currency_iso_code')] + public function getCurrencyISOCode(): ?string + { + return $this->currency?->getIsoCode(); + } + /******************************************************************************** * * Setters diff --git a/src/Entity/ProjectSystem/Project.php b/src/Entity/ProjectSystem/Project.php index 4d79ee38..53f74af0 100644 --- a/src/Entity/ProjectSystem/Project.php +++ b/src/Entity/ProjectSystem/Project.php @@ -22,8 +22,21 @@ declare(strict_types=1); namespace App\Entity\ProjectSystem; +use Doctrine\Common\Collections\Criteria; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\Parts\DeviceRepository; use App\Validator\Constraints\UniqueObjectCollection; use Doctrine\DBAL\Types\Types; @@ -36,6 +49,8 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use InvalidArgumentException; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\TypeInfo\Type\NullableType; +use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; @@ -46,21 +61,56 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; */ #[ORM\Entity(repositoryClass: DeviceRepository::class)] #[ORM\Table(name: 'projects')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@projects.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['project:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['project:write', 'api:basic:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/projects/{id}/children.{_format}', + operations: [ + new GetCollection( + openapi: new Operation(summary: 'Retrieves the children elements of a project.'), + security: 'is_granted("@projects.read")' + ) + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: Project::class) + ], + normalizationContext: ['groups' => ['project:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class Project extends AbstractStructuralDBElement { - #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['project:read', 'project:write'])] + #[ApiProperty(readableLink: false, writableLink: false, nativeType: new NullableType(new ObjectType(self::class)))] protected ?AbstractStructuralDBElement $parent = null; + #[Groups(['project:read', 'project:write'])] + protected string $comment = ''; + + /** + * @var Collection + */ #[Assert\Valid] - #[Groups(['extended', 'full'])] - #[ORM\OneToMany(targetEntity: ProjectBOMEntry::class, mappedBy: 'project', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[UniqueObjectCollection(fields: ['part'], message: 'project.bom_entry.part_already_in_bom')] - #[UniqueObjectCollection(fields: ['name'], message: 'project.bom_entry.name_already_in_bom')] + #[Groups(['extended', 'full', 'import'])] + #[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectBOMEntry::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[UniqueObjectCollection(message: 'project.bom_entry.part_already_in_bom', fields: ['part'])] + #[UniqueObjectCollection(message: 'project.bom_entry.name_already_in_bom', fields: ['name'])] protected Collection $bom_entries; #[ORM\Column(type: Types::INTEGER)] @@ -70,7 +120,7 @@ class Project extends AbstractStructuralDBElement * @var string|null The current status of the project */ #[Assert\Choice(['draft', 'planning', 'in_production', 'finished', 'archived'])] - #[Groups(['extended', 'full'])] + #[Groups(['extended', 'full', 'project:read', 'project:write', 'import'])] #[ORM\Column(type: Types::STRING, length: 64, nullable: true)] protected ?string $status = null; @@ -78,33 +128,43 @@ class Project extends AbstractStructuralDBElement /** * @var Part|null The (optional) part that represents the builds of this project in the stock */ - #[ORM\OneToOne(targetEntity: Part::class, mappedBy: 'built_project', cascade: ['persist'], orphanRemoval: true)] + #[ORM\OneToOne(mappedBy: 'built_project', targetEntity: Part::class, cascade: ['persist'], orphanRemoval: true)] + #[Groups(['project:read', 'project:write'])] protected ?Part $build_part = null; #[ORM\Column(type: Types::BOOLEAN)] protected bool $order_only_missing_parts = false; - #[Groups(['simple', 'extended', 'full'])] + #[Groups(['simple', 'extended', 'full', 'project:read', 'project:write'])] #[ORM\Column(type: Types::TEXT)] protected string $description = ''; /** * @var Collection */ - #[ORM\OneToMany(targetEntity: ProjectAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: ProjectAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => Criteria::ASC])] + #[Groups(['project:read', 'project:write'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: ProjectAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['project:read', 'project:write'])] protected ?Attachment $master_picture_attachment = null; /** @var Collection */ - #[ORM\OneToMany(targetEntity: ProjectParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: ProjectParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] + #[Groups(['project:read', 'project:write'])] protected Collection $parameters; + #[Groups(['project:read'])] + protected ?\DateTimeImmutable $addedDate = null; + #[Groups(['project:read'])] + protected ?\DateTimeImmutable $lastModified = null; + + /******************************************************************************** * * Getters @@ -129,7 +189,7 @@ class Project extends AbstractStructuralDBElement //Set master attachment is needed foreach ($bom_entries as $bom_entry) { $clone = clone $bom_entry; - $this->bom_entries->add($clone); + $this->addBomEntry($clone); } } @@ -275,7 +335,6 @@ class Project extends AbstractStructuralDBElement { //If this project has subprojects, and these have builds part, they must be included in the BOM foreach ($this->getChildren() as $child) { - /** @var $child Project */ if (!$child->getBuildPart() instanceof Part) { continue; } diff --git a/src/Entity/ProjectSystem/ProjectBOMEntry.php b/src/Entity/ProjectSystem/ProjectBOMEntry.php index 8955b1cf..2a7862ec 100644 --- a/src/Entity/ProjectSystem/ProjectBOMEntry.php +++ b/src/Entity/ProjectSystem/ProjectBOMEntry.php @@ -22,6 +22,20 @@ declare(strict_types=1); namespace App\Entity\ProjectSystem; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Doctrine\Orm\Filter\RangeFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; +use App\Entity\Contracts\TimeStampableInterface; use App\Validator\UniqueValidatableInterface; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; @@ -32,7 +46,7 @@ use App\Validator\Constraints\BigDecimal\BigDecimalPositive; use App\Validator\Constraints\Selectable; use Brick\Math\BigDecimal; use Doctrine\ORM\Mapping as ORM; -use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; @@ -42,18 +56,48 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ORM\HasLifecycleCallbacks] #[ORM\Entity] #[ORM\Table('project_bom_entries')] -class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInterface +#[ApiResource( + operations: [ + new Get(uriTemplate: '/project_bom_entries/{id}.{_format}', security: 'is_granted("read", object)',), + new GetCollection(uriTemplate: '/project_bom_entries.{_format}', security: 'is_granted("@projects.read")',), + new Post(uriTemplate: '/project_bom_entries.{_format}', securityPostDenormalize: 'is_granted("create", object)',), + new Patch(uriTemplate: '/project_bom_entries/{id}.{_format}', security: 'is_granted("edit", object)',), + new Delete(uriTemplate: '/project_bom_entries/{id}.{_format}', security: 'is_granted("delete", object)',), + ], + normalizationContext: ['groups' => ['bom_entry:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['bom_entry:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/projects/{id}/bom.{_format}', + operations: [ + new GetCollection( + openapi: new Operation(summary: 'Retrieves the BOM entries of the given project.'), + security: 'is_granted("@projects.read")' + ) + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'bom_entries', fromClass: Project::class) + ], + normalizationContext: ['groups' => ['bom_entry:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment", 'mountnames'])] +#[ApiFilter(RangeFilter::class, properties: ['quantity'])] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified', 'quantity'])] +class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInterface, TimeStampableInterface { use TimestampTrait; #[Assert\Positive] - #[ORM\Column(type: Types::FLOAT, name: 'quantity')] + #[ORM\Column(name: 'quantity', type: Types::FLOAT)] + #[Groups(['bom_entry:read', 'bom_entry:write', 'import', 'simple', 'extended', 'full'])] protected float $quantity = 1.0; /** * @var string A comma separated list of the names, where this parts should be placed */ - #[ORM\Column(type: Types::TEXT, name: 'mountnames')] + #[ORM\Column(name: 'mountnames', type: Types::TEXT)] + #[Groups(['bom_entry:read', 'bom_entry:write', 'import', 'simple', 'extended', 'full'])] protected string $mountnames = ''; /** @@ -61,12 +105,14 @@ class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInte */ #[Assert\Expression('this.getPart() !== null or this.getName() !== null', message: 'validator.project.bom_entry.name_or_part_needed')] #[ORM\Column(type: Types::STRING, nullable: true)] + #[Groups(['bom_entry:read', 'bom_entry:write', 'import', 'simple', 'extended', 'full'])] protected ?string $name = null; /** * @var string An optional comment for this BOM entry */ #[ORM\Column(type: Types::TEXT)] + #[Groups(['bom_entry:read', 'bom_entry:write', 'import', 'extended', 'full'])] protected string $comment = ''; /** @@ -74,6 +120,7 @@ class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInte */ #[ORM\ManyToOne(targetEntity: Project::class, inversedBy: 'bom_entries')] #[ORM\JoinColumn(name: 'id_device')] + #[Groups(['bom_entry:read', 'bom_entry:write', ])] protected ?Project $project = null; /** @@ -81,6 +128,7 @@ class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInte */ #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'project_bom_entries')] #[ORM\JoinColumn(name: 'id_part')] + #[Groups(['bom_entry:read', 'bom_entry:write', 'full'])] protected ?Part $part = null; /** @@ -88,6 +136,7 @@ class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInte */ #[Assert\AtLeastOneOf([new BigDecimalPositive(), new Assert\IsNull()])] #[ORM\Column(type: 'big_decimal', precision: 11, scale: 5, nullable: true)] + #[Groups(['bom_entry:read', 'bom_entry:write', 'import', 'extended', 'full'])] protected ?BigDecimal $price = null; /** diff --git a/src/Entity/SettingsEntry.php b/src/Entity/SettingsEntry.php new file mode 100644 index 00000000..488de1d1 --- /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/ApiToken.php b/src/Entity/UserSystem/ApiToken.php new file mode 100644 index 00000000..f5cbf541 --- /dev/null +++ b/src/Entity/UserSystem/ApiToken.php @@ -0,0 +1,199 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity\UserSystem; + +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\Entity\Base\TimestampTrait; +use App\Entity\Contracts\TimeStampableInterface; +use App\Repository\UserSystem\ApiTokenRepository; +use App\State\CurrentApiTokenProvider; +use App\Validator\Constraints\Year2038BugWorkaround; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\NotBlank; + +#[ORM\Entity(repositoryClass: ApiTokenRepository::class)] +#[ORM\Table(name: 'api_tokens')] +#[ORM\HasLifecycleCallbacks] +#[UniqueEntity(fields: ['name', 'user'])] + +#[ApiResource( + uriTemplate: '/tokens/current.{_format}', + description: 'A token used to authenticate API requests.', + operations: [new Get( + openapi: new Operation(summary: 'Get information about the API token that is currently used.'), + )], + normalizationContext: ['groups' => ['token:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + provider: CurrentApiTokenProvider::class, +)] +#[ApiFilter(PropertyFilter::class)] +class ApiToken implements TimeStampableInterface +{ + + use TimestampTrait; + + #[ORM\Id] + #[ORM\Column(type: Types::INTEGER)] + #[ORM\GeneratedValue] + protected int $id; + + #[ORM\Column(type: Types::STRING)] + #[Length(max: 255)] + #[NotBlank] + #[Groups('token:read')] + protected string $name = ''; + + #[ORM\ManyToOne(inversedBy: 'api_tokens')] + #[Groups('token:read')] + private ?User $user = null; + + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] + #[Groups('token:read')] + #[Year2038BugWorkaround] + private ?\DateTimeImmutable $valid_until; + + #[ORM\Column(length: 68, unique: true)] + private string $token; + + #[ORM\Column(type: Types::SMALLINT, enumType: ApiTokenLevel::class)] + #[Groups('token:read')] + private ApiTokenLevel $level = ApiTokenLevel::READ_ONLY; + + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] + #[Groups('token:read')] + private ?\DateTimeImmutable $last_time_used = null; + + public function __construct(ApiTokenType $tokenType = ApiTokenType::PERSONAL_ACCESS_TOKEN) + { + // Generate a rondom token on creation. The tokenType is 3 characters long (plus underscore), so the token is 68 characters long. + $this->token = $tokenType->getTokenPrefix() . bin2hex(random_bytes(32)); + + //By default, tokens are valid for 1 year. + $this->valid_until = new \DateTimeImmutable('+1 year'); + } + + public function getTokenType(): ApiTokenType + { + return ApiTokenType::getTypeFromToken($this->token); + } + + public function getUser(): ?User + { + return $this->user; + } + + public function setUser(?User $user): ApiToken + { + $this->user = $user; + return $this; + } + + public function getValidUntil(): ?\DateTimeImmutable + { + return $this->valid_until; + } + + /** + * Checks if the token is still valid. + * @return bool + */ + public function isValid(): bool + { + return $this->valid_until === null || $this->valid_until > new \DateTimeImmutable(); + } + + public function setValidUntil(?\DateTimeImmutable $valid_until): ApiToken + { + $this->valid_until = $valid_until; + return $this; + } + + public function getToken(): string + { + return $this->token; + } + + public function getId(): int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): ApiToken + { + $this->name = $name; + return $this; + } + + /** + * Gets the last time the token was used to authenticate or null if it was never used. + */ + public function getLastTimeUsed(): ?\DateTimeImmutable + { + return $this->last_time_used; + } + + /** + * Sets the last time the token was used to authenticate. + * @return ApiToken + */ + public function setLastTimeUsed(?\DateTimeImmutable $last_time_used): ApiToken + { + $this->last_time_used = $last_time_used; + return $this; + } + + public function getLevel(): ApiTokenLevel + { + return $this->level; + } + + public function setLevel(ApiTokenLevel $level): ApiToken + { + $this->level = $level; + return $this; + } + + /** + * Returns the last 4 characters of the token secret, which can be used to identify the token. + * @return string + */ + public function getLastTokenChars(): string + { + return substr($this->token, -4); + } + + +} \ No newline at end of file diff --git a/src/Entity/UserSystem/ApiTokenLevel.php b/src/Entity/UserSystem/ApiTokenLevel.php new file mode 100644 index 00000000..3f997300 --- /dev/null +++ b/src/Entity/UserSystem/ApiTokenLevel.php @@ -0,0 +1,73 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity\UserSystem; + +enum ApiTokenLevel: int +{ + private const ROLE_READ_ONLY = 'ROLE_API_READ_ONLY'; + private const ROLE_EDIT = 'ROLE_API_EDIT'; + private const ROLE_ADMIN = 'ROLE_API_ADMIN'; + private const ROLE_FULL = 'ROLE_API_FULL'; + + /** + * The token can only read (non-sensitive) data. + */ + case READ_ONLY = 1; + /** + * The token can read and edit (non-sensitive) data. + */ + case EDIT = 2; + /** + * The token can do some administrative tasks (like viewing all log entries), but can not change passwords and create new tokens. + */ + case ADMIN = 3; + /** + * The token can do everything the user can do. + */ + case FULL = 4; + + /** + * Returns the additional roles that the authenticated user should have when using this token. + * @return string[] + */ + public function getAdditionalRoles(): array + { + //The higher roles should always include the lower ones + return match ($this) { + self::READ_ONLY => [self::ROLE_READ_ONLY], + self::EDIT => [self::ROLE_READ_ONLY, self::ROLE_EDIT], + self::ADMIN => [self::ROLE_READ_ONLY, self::ROLE_EDIT, self::ROLE_ADMIN], + self::FULL => [self::ROLE_READ_ONLY, self::ROLE_EDIT, self::ROLE_ADMIN, self::ROLE_FULL], + }; + } + + /** + * Returns the translation key for the name of this token level. + * @return string + */ + public function getTranslationKey(): string + { + return 'api_token.level.' . strtolower($this->name); + } +} \ No newline at end of file diff --git a/src/Entity/UserSystem/ApiTokenType.php b/src/Entity/UserSystem/ApiTokenType.php new file mode 100644 index 00000000..f8beb378 --- /dev/null +++ b/src/Entity/UserSystem/ApiTokenType.php @@ -0,0 +1,56 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity\UserSystem; + +/** + * The type of ApiToken. + * The enum value is the prefix of the token. It must be 3 characters long. + */ +enum ApiTokenType: string +{ + case PERSONAL_ACCESS_TOKEN = 'tcp'; + + /** + * Get the prefix of the token including the underscore + * @return string + */ + public function getTokenPrefix(): string + { + return $this->value . '_'; + } + + /** + * Get the type from the token prefix + * @param string $api_token + * @return ApiTokenType + */ + public static function getTypeFromToken(string $api_token): ApiTokenType + { + $parts = explode('_', $api_token); + if (count($parts) !== 2) { + throw new \InvalidArgumentException('Invalid token format'); + } + return self::from($parts[0]); + } +} diff --git a/src/Entity/UserSystem/Group.php b/src/Entity/UserSystem/Group.php index 01e79498..6da9d35f 100644 --- a/src/Entity/UserSystem/Group.php +++ b/src/Entity/UserSystem/Group.php @@ -22,8 +22,8 @@ declare(strict_types=1); namespace App\Entity\UserSystem; +use Doctrine\Common\Collections\Criteria; use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentTypeAttachment; use App\Validator\Constraints\NoLockout; use Doctrine\DBAL\Types\Types; use App\Entity\Attachments\GroupAttachment; @@ -44,13 +44,13 @@ use Symfony\Component\Validator\Constraints as Assert; */ #[ORM\Entity] #[ORM\Table('`groups`')] -#[ORM\Index(name: 'group_idx_name', columns: ['name'])] -#[ORM\Index(name: 'group_idx_parent_name', columns: ['parent_id', 'name'])] -#[NoLockout()] +#[ORM\Index(columns: ['name'], name: 'group_idx_name')] +#[ORM\Index(columns: ['parent_id', 'name'], name: 'group_idx_parent_name')] +#[NoLockout] class Group extends AbstractStructuralDBElement implements HasPermissionsInterface { - #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -60,22 +60,22 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa /** * @var Collection */ - #[ORM\OneToMany(targetEntity: User::class, mappedBy: 'group')] + #[ORM\OneToMany(mappedBy: 'group', targetEntity: User::class)] protected Collection $users; /** * @var bool If true all users associated with this group must have enabled some kind of two-factor authentication */ #[Groups(['extended', 'full', 'import'])] - #[ORM\Column(type: Types::BOOLEAN, name: 'enforce_2fa')] + #[ORM\Column(name: 'enforce_2fa', type: Types::BOOLEAN)] protected bool $enforce2FA = false; /** * @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: GroupAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: GroupAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: GroupAttachment::class)] @@ -84,15 +84,15 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa #[Groups(['full'])] #[ORM\Embedded(class: PermissionData::class, columnPrefix: 'permissions_')] - #[ValidPermission()] + #[ValidPermission] protected ?PermissionData $permissions = null; /** * @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: GroupParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: GroupParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] protected Collection $parameters; public function __construct() diff --git a/src/Entity/UserSystem/PermissionData.php b/src/Entity/UserSystem/PermissionData.php index 38f4b774..9ebdc9c9 100644 --- a/src/Entity/UserSystem/PermissionData.php +++ b/src/Entity/UserSystem/PermissionData.php @@ -57,7 +57,7 @@ final class PermissionData implements \JsonSerializable * operation => value, * ] */ - #[ORM\Column(type: Types::JSON, name: 'data')] + #[ORM\Column(name: 'data', type: Types::JSON)] protected array $data = [] ) { @@ -145,8 +145,8 @@ final class PermissionData implements \JsonSerializable */ public function setPermissionValue(string $permission, string $operation, ?bool $value): self { - if ($value === null) { - //If the value is null, unset the permission value (meaning implicit inherit) + //If the value is null, unset the permission value, if it was set befoere (meaning implicit inherit) + if ($value === null && isset($this->data[$permission][$operation])) { unset($this->data[$permission][$operation]); } else { //Otherwise, set the pemission value diff --git a/src/Entity/UserSystem/U2FKey.php b/src/Entity/UserSystem/U2FKey.php index f6a2a2e4..d1d864bc 100644 --- a/src/Entity/UserSystem/U2FKey.php +++ b/src/Entity/UserSystem/U2FKey.php @@ -22,16 +22,18 @@ declare(strict_types=1); namespace App\Entity\UserSystem; +use App\Entity\Contracts\TimeStampableInterface; use Doctrine\DBAL\Types\Types; use App\Entity\Base\TimestampTrait; use Doctrine\ORM\Mapping as ORM; use Jbtronics\TFAWebauthn\Model\LegacyU2FKeyInterface; +use Symfony\Component\Validator\Constraints\Length; #[ORM\Entity] #[ORM\HasLifecycleCallbacks] #[ORM\Table(name: 'u2f_keys')] #[ORM\UniqueConstraint(name: 'user_unique', columns: ['user_id', 'key_handle'])] -class U2FKey implements LegacyU2FKeyInterface +class U2FKey implements LegacyU2FKeyInterface, TimeStampableInterface { use TimestampTrait; @@ -43,6 +45,7 @@ class U2FKey implements LegacyU2FKeyInterface * @var string **/ #[ORM\Column(type: Types::STRING, length: 128)] + #[Length(max: 128)] public string $keyHandle = ''; /** diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index b1dc63ad..78f89347 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -22,8 +22,19 @@ declare(strict_types=1); namespace App\Entity\UserSystem; +use Doctrine\Common\Collections\Criteria; +use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\UserRepository; use App\EntityListeners\TreeCacheInvalidationListener; use App\Validator\Constraints\NoLockout; @@ -40,6 +51,7 @@ use Jbtronics\TFAWebauthn\Model\LegacyU2FKeyInterface; use Nbgrp\OneloginSamlBundle\Security\User\SamlUserInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints\Length; use Webauthn\PublicKeyCredentialUserEntity; use function count; use DateTime; @@ -68,12 +80,29 @@ use Jbtronics\TFAWebauthn\Model\TwoFactorInterface as WebauthnTwoFactorInterface #[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\EntityListeners([TreeCacheInvalidationListener::class])] #[ORM\Table('`users`')] -#[ORM\Index(name: 'user_idx_username', columns: ['name'])] +#[ORM\Index(columns: ['name'], name: 'user_idx_username')] #[ORM\AttributeOverrides([ new ORM\AttributeOverride(name: 'name', column: new ORM\Column(type: Types::STRING, length: 180, unique: true)) ])] - -#[NoLockout()] +#[ApiResource( + shortName: 'User', + operations: [ + new Get( + openapi: new Operation(summary: 'Get information about the current user.'), + security: 'is_granted("read", object)' + ), + new GetCollection( + openapi: new Operation(summary: 'Get all users defined in the system.'), + security: 'is_granted("@users.read")' + ), + ], + normalizationContext: ['groups' => ['user:read'], 'openapi_definition_name' => 'Read'], +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "aboutMe"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] +#[NoLockout(groups: ['permissions:edit'])] class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface, TwoFactorInterface, BackupCodeInterface, TrustedDeviceInterface, WebauthnTwoFactorInterface, PreferredProviderInterface, PasswordAuthenticatedUserInterface, SamlUserInterface { @@ -84,19 +113,28 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe */ final public const ID_ANONYMOUS = 1; + #[Groups(['user:read'])] + protected ?int $id = null; + + #[Groups(['user:read'])] + protected ?\DateTimeImmutable $lastModified = null; + + #[Groups(['user:read'])] + protected ?\DateTimeImmutable $addedDate = null; + /** * @var bool Determines if the user is disabled (user can not log in) */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'user:read'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $disabled = false; /** * @var string|null The theme */ - #[Groups(['full', 'import'])] - #[ORM\Column(type: Types::STRING, name: 'config_theme', nullable: true)] - #[ValidTheme()] + #[Groups(['full', 'import', 'user:read'])] + #[ORM\Column(name: 'config_theme', type: Types::STRING, nullable: true)] + #[ValidTheme] protected ?string $theme = null; /** @@ -105,16 +143,18 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe #[ORM\Column(type: Types::STRING, nullable: true)] protected ?string $pw_reset_token = null; - #[ORM\Column(type: Types::TEXT, name: 'config_instock_comment_a')] + #[ORM\Column(name: 'config_instock_comment_a', type: Types::TEXT)] + #[Groups(['extended', 'full', 'import'])] protected string $instock_comment_a = ''; - #[ORM\Column(type: Types::TEXT, name: 'config_instock_comment_w')] + #[ORM\Column(name: 'config_instock_comment_w', type: Types::TEXT)] + #[Groups(['extended', 'full', 'import'])] protected string $instock_comment_w = ''; /** - * @var string A self-description of the user + * @var string A self-description of the user as markdown text */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'user:read'])] #[ORM\Column(type: Types::TEXT)] protected string $aboutMe = ''; @@ -133,10 +173,11 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * @var Group|null the group this user belongs to * DO NOT PUT A fetch eager here! Otherwise, you can not unset the group of a user! This seems to be some kind of bug in doctrine. Maybe this is fixed in future versions. */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'user:read'])] #[ORM\ManyToOne(targetEntity: Group::class, inversedBy: 'users')] #[ORM\JoinColumn(name: 'group_id')] #[Selectable] + #[ApiProperty(readableLink: true, writableLink: false)] protected ?Group $group = null; /** @@ -149,57 +190,62 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * @var string|null The timezone the user prefers */ #[Assert\Timezone] - #[Groups(['full', 'import'])] - #[ORM\Column(type: Types::STRING, name: 'config_timezone', nullable: true)] + #[Groups(['full', 'import', 'user:read'])] + #[ORM\Column(name: 'config_timezone', type: Types::STRING, nullable: true)] protected ?string $timezone = ''; /** * @var string|null The language/locale the user prefers */ - #[Assert\Language] - #[Groups(['full', 'import'])] - #[ORM\Column(type: Types::STRING, name: 'config_language', nullable: true)] + #[Assert\Locale] + #[Groups(['full', 'import', 'user:read'])] + #[ORM\Column(name: 'config_language', type: Types::STRING, nullable: true)] protected ?string $language = ''; /** * @var string|null The email address of the user */ #[Assert\Email] - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'user:read'])] #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] + #[Length(max: 255)] protected ?string $email = ''; /** * @var bool True if the user wants to show his email address on his (public) profile */ #[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])] + #[Groups(['full', 'import', 'user:read'])] protected bool $show_email_on_profile = false; /** * @var string|null The department the user is working */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'user:read'])] #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] + #[Length(max: 255)] protected ?string $department = ''; /** * @var string|null The last name of the User */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'user:read'])] #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] + #[Length(max: 255)] protected ?string $last_name = ''; /** * @var string|null The first name of the User */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'user:read'])] #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] + #[Length(max: 255)] protected ?string $first_name = ''; /** * @var bool True if the user needs to change password after log in */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'user:read'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $need_pw_change = true; @@ -210,7 +256,8 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe protected ?string $password = null; #[Assert\NotBlank] - #[Assert\Regex('/^[\w\.\+\-\$]+$/', message: 'user.invalid_username')] + #[Assert\Regex('/^[\w\.\+\-\$]+[\w\.\+\-\$\@]*$/', message: 'user.invalid_username')] + #[Groups(['user:read'])] protected string $name = ''; /** @@ -223,18 +270,20 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * @var Collection */ #[ORM\OneToMany(mappedBy: 'element', targetEntity: UserAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] + #[Groups(['user:read', 'user:write'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: UserAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['user:read', 'user:write'])] protected ?Attachment $master_picture_attachment = null; - /** @var \DateTimeInterface|null The time when the backup codes were generated + /** @var \DateTimeImmutable|null The time when the backup codes were generated */ #[Groups(['full'])] - #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] - protected ?\DateTimeInterface $backupCodesGenerationDate = null; + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] + protected ?\DateTimeImmutable $backupCodesGenerationDate = null; /** @var Collection */ @@ -247,6 +296,12 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe #[ORM\OneToMany(mappedBy: 'user', targetEntity: WebauthnKey::class, cascade: ['REMOVE'], fetch: 'EXTRA_LAZY', orphanRemoval: true)] protected Collection $webauthn_keys; + /** + * @var Collection + */ + #[ORM\OneToMany(mappedBy: 'user', targetEntity: ApiToken::class, cascade: ['REMOVE'], fetch: 'EXTRA_LAZY', orphanRemoval: true)] + private Collection $api_tokens; + /** * @var Currency|null The currency the user wants to see prices in. * Dont use fetch=EAGER here, this will cause problems with setting the currency setting. @@ -261,14 +316,14 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe #[Groups(['simple', 'extended', 'full', 'import'])] #[ORM\Embedded(class: 'PermissionData', columnPrefix: 'permissions_')] - #[ValidPermission()] + #[ValidPermission] protected ?PermissionData $permissions = null; /** - * @var \DateTimeInterface|null the time until the password reset token is valid + * @var \DateTimeImmutable|null the time until the password reset token is valid */ - #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] - protected ?\DateTimeInterface $pw_reset_expires = null; + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] + protected ?\DateTimeImmutable $pw_reset_expires = null; /** * @var bool True if the user was created by a SAML provider (and therefore cannot change its password) @@ -284,6 +339,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe $this->permissions = new PermissionData(); $this->u2fKeys = new ArrayCollection(); $this->webauthn_keys = new ArrayCollection(); + $this->api_tokens = new ArrayCollection(); } /** @@ -474,7 +530,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Gets the datetime when the password reset token expires. */ - public function getPwResetExpires(): \DateTimeInterface|null + public function getPwResetExpires(): \DateTimeImmutable|null { return $this->pw_reset_expires; } @@ -482,7 +538,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Sets the datetime when the password reset token expires. */ - public function setPwResetExpires(\DateTimeInterface $pw_reset_expires): self + public function setPwResetExpires(\DateTimeImmutable $pw_reset_expires): self { $this->pw_reset_expires = $pw_reset_expires; @@ -501,6 +557,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * * @return string a string with the full name of this user */ + #[Groups(['user:read'])] public function getFullName(bool $including_username = false): string { $tmp = $this->getFirstName(); @@ -836,13 +893,11 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * @param string[] $codes An array containing the backup codes * * @return $this - * - * @throws Exception If an error with the datetime occurs */ public function setBackupCodes(array $codes): self { $this->backupCodes = $codes; - $this->backupCodesGenerationDate = $codes === [] ? null : new DateTime(); + $this->backupCodesGenerationDate = $codes === [] ? null : new \DateTimeImmutable(); return $this; } @@ -850,7 +905,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Return the date when the backup codes were generated. */ - public function getBackupCodesGenerationDate(): ?\DateTimeInterface + public function getBackupCodesGenerationDate(): ?\DateTimeImmutable { return $this->backupCodesGenerationDate; } @@ -936,8 +991,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe return $this; } - - public function setSamlAttributes(array $attributes): void { //When mail attribute exists, set it @@ -967,4 +1020,34 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe $this->setEmail($attributes['urn:oid:1.2.840.113549.1.9.1'][0]); } } + + /** + * Return all API tokens of the user. + * @return Collection + */ + public function getApiTokens(): Collection + { + return $this->api_tokens; + } + + /** + * Add an API token to the user. + * @param ApiToken $apiToken + * @return void + */ + public function addApiToken(ApiToken $apiToken): void + { + $apiToken->setUser($this); + $this->api_tokens->add($apiToken); + } + + /** + * Remove an API token from the user. + * @param ApiToken $apiToken + * @return void + */ + public function removeApiToken(ApiToken $apiToken): void + { + $this->api_tokens->removeElement($apiToken); + } } diff --git a/src/Entity/UserSystem/WebauthnKey.php b/src/Entity/UserSystem/WebauthnKey.php index ee467bc3..7d3cb7b3 100644 --- a/src/Entity/UserSystem/WebauthnKey.php +++ b/src/Entity/UserSystem/WebauthnKey.php @@ -22,16 +22,18 @@ declare(strict_types=1); */ namespace App\Entity\UserSystem; +use App\Entity\Contracts\TimeStampableInterface; use Doctrine\DBAL\Types\Types; use App\Entity\Base\TimestampTrait; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; use Webauthn\PublicKeyCredentialSource as BasePublicKeyCredentialSource; #[ORM\Entity] #[ORM\HasLifecycleCallbacks] #[ORM\Table(name: 'webauthn_keys')] -class WebauthnKey extends BasePublicKeyCredentialSource +class WebauthnKey extends BasePublicKeyCredentialSource implements TimeStampableInterface { use TimestampTrait; @@ -42,11 +44,15 @@ class WebauthnKey extends BasePublicKeyCredentialSource #[ORM\Column(type: Types::STRING)] #[NotBlank] + #[Length(max: 255)] protected string $name = ''; #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'webauthn_keys')] protected ?User $user = null; + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] + protected ?\DateTimeImmutable $last_time_used = null; + public function getName(): string { return $this->name; @@ -74,23 +80,39 @@ class WebauthnKey extends BasePublicKeyCredentialSource return $this->id; } + /** + * Retrieve the last time when the key was used. + */ + public function getLastTimeUsed(): ?\DateTimeImmutable + { + return $this->last_time_used; + } - - + /** + * Update the last time when the key was used. + * @return void + */ + public function updateLastTimeUsed(): void + { + $this->last_time_used = new \DateTimeImmutable('now'); + } 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 e9df5972..1f39b2d0 100644 --- a/src/EntityListeners/AttachmentDeleteListener.php +++ b/src/EntityListeners/AttachmentDeleteListener.php @@ -52,8 +52,8 @@ class AttachmentDeleteListener #[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 00000000..08a93f76 --- /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/EntityListeners/TreeCacheInvalidationListener.php b/src/EntityListeners/TreeCacheInvalidationListener.php index 4cbcf8f8..eae7ce35 100644 --- a/src/EntityListeners/TreeCacheInvalidationListener.php +++ b/src/EntityListeners/TreeCacheInvalidationListener.php @@ -24,49 +24,52 @@ namespace App\EntityListeners; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractStructuralDBElement; -use App\Entity\LabelSystem\LabelProfile; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; -use App\Services\UserSystem\UserCacheKeyGenerator; -use Doctrine\ORM\Event\LifecycleEventArgs; +use App\Services\Cache\ElementCacheTagGenerator; +use App\Services\Cache\UserCacheKeyGenerator; +use Doctrine\ORM\Event\PostPersistEventArgs; +use Doctrine\ORM\Event\PostRemoveEventArgs; +use Doctrine\ORM\Event\PostUpdateEventArgs; use Doctrine\ORM\Mapping as ORM; -use function get_class; use Symfony\Contracts\Cache\TagAwareCacheInterface; class TreeCacheInvalidationListener { - public function __construct(protected TagAwareCacheInterface $cache, protected UserCacheKeyGenerator $keyGenerator) + public function __construct( + protected TagAwareCacheInterface $cache, + protected UserCacheKeyGenerator $keyGenerator, + protected ElementCacheTagGenerator $tagGenerator + ) { } #[ORM\PostUpdate] #[ORM\PostPersist] #[ORM\PostRemove] - public function invalidate(AbstractDBElement $element, LifecycleEventArgs $event): void + public function invalidate(AbstractDBElement $element, PostUpdateEventArgs|PostPersistEventArgs|PostRemoveEventArgs $event): void { - //If an element was changed, then invalidate all cached trees with this element class - if ($element instanceof AbstractStructuralDBElement || $element instanceof LabelProfile) { - $secure_class_name = str_replace('\\', '_', $element::class); - $this->cache->invalidateTags([$secure_class_name]); + //For all changes, we invalidate the cache for all elements of this class + $tags = [$this->tagGenerator->getElementTypeCacheTag($element)]; - //Trigger a sidebar reload for all users (see SidebarTreeUpdater service) - if(!$element instanceof LabelProfile) { - $this->cache->invalidateTags(['sidebar_tree_update']); - } + + //For changes on structural elements, we also invalidate the sidebar tree + if ($element instanceof AbstractStructuralDBElement) { + $tags[] = 'sidebar_tree_update'; } - //If a user change, then invalidate all cached trees for him + //For user changes, we invalidate the cache for this user if ($element instanceof User) { - $secure_class_name = str_replace('\\', '_', $element::class); - $tag = $this->keyGenerator->generateKey($element); - $this->cache->invalidateTags([$tag, $secure_class_name]); + $tags[] = $this->keyGenerator->generateKey($element); } /* If any group change, then invalidate all cached trees. Users Permissions can be inherited from groups, so a change in any group can cause big permisssion changes for users. So to be sure, invalidate all trees */ if ($element instanceof Group) { - $tag = 'groups'; - $this->cache->invalidateTags([$tag]); + $tags[] = 'groups'; } + + //Invalidate the cache for the given tags + $this->cache->invalidateTags($tags); } } diff --git a/src/EnvVarProcessors/AddSlashEnvVarProcessor.php b/src/EnvVarProcessors/AddSlashEnvVarProcessor.php new file mode 100644 index 00000000..aaf0abc9 --- /dev/null +++ b/src/EnvVarProcessors/AddSlashEnvVarProcessor.php @@ -0,0 +1,49 @@ +. + */ + +declare(strict_types=1); + + +namespace App\EnvVarProcessors; + +use Symfony\Component\DependencyInjection\EnvVarProcessorInterface; + +/** + * Env var processor that adds a trailing slash to a string if not already present. + */ +final class AddSlashEnvVarProcessor implements EnvVarProcessorInterface +{ + + public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed + { + $env = $getEnv($name); + if (!is_string($env)) { + throw new \InvalidArgumentException(sprintf('The "addSlash" env var processor only works with strings, got %s.', gettype($env))); + } + return rtrim($env, '/') . '/'; + } + + public static function getProvidedTypes(): array + { + return [ + 'addSlash' => 'string', + ]; + } +} diff --git a/src/Services/CustomEnvVarProcessor.php b/src/EnvVarProcessors/CustomEnvVarProcessor.php similarity index 98% rename from src/Services/CustomEnvVarProcessor.php rename to src/EnvVarProcessors/CustomEnvVarProcessor.php index f269cc7d..55a6b94d 100644 --- a/src/Services/CustomEnvVarProcessor.php +++ b/src/EnvVarProcessors/CustomEnvVarProcessor.php @@ -20,7 +20,7 @@ declare(strict_types=1); -namespace App\Services; +namespace App\EnvVarProcessors; use Closure; use Symfony\Component\DependencyInjection\EnvVarProcessorInterface; diff --git a/src/EventListener/AddEditCommentRequestListener.php b/src/EventListener/AddEditCommentRequestListener.php new file mode 100644 index 00000000..33c72b3f --- /dev/null +++ b/src/EventListener/AddEditCommentRequestListener.php @@ -0,0 +1,62 @@ +. + */ + +declare(strict_types=1); + + +namespace App\EventListener; + +use App\Services\LogSystem\EventCommentHelper; +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; +use Symfony\Component\HttpKernel\Event\RequestEvent; + +#[AsEventListener] +class AddEditCommentRequestListener +{ + public function __construct(private readonly EventCommentHelper $helper) + { + + } + + public function __invoke(RequestEvent $event) + { + if (!$event->isMainRequest()) { + return; + } + $request = $event->getRequest(); + + //Do not add comment if the request is a GET request + if ($request->isMethod('GET')) { + return; + } + + //Check if the user tries to access a /api/ endpoint, if not skip + if (!str_contains($request->getPathInfo(), '/api/')) { + return; + } + + //Extract the comment from the query parameter + $comment = $request->query->getString('_comment', ''); + + if ($comment !== '') { + $this->helper->setMessage($comment); + } + } +} \ No newline at end of file diff --git a/src/EventListener/AllowSlowNaturalSortListener.php b/src/EventListener/AllowSlowNaturalSortListener.php new file mode 100644 index 00000000..02ec6144 --- /dev/null +++ b/src/EventListener/AllowSlowNaturalSortListener.php @@ -0,0 +1,49 @@ +. + */ + +declare(strict_types=1); + + +namespace App\EventListener; + +use App\Doctrine\Functions\Natsort; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; +use Symfony\Component\HttpKernel\Event\RequestEvent; + +/** + * This is a workaround to the fact that we can not inject parameters into doctrine custom functions. + * Therefore we use this event listener to call the static function on the custom function, to inject the value, before + * any NATSORT function is called. + */ +#[AsEventListener] +class AllowSlowNaturalSortListener +{ + public function __construct( + #[Autowire(param: 'partdb.db.emulate_natural_sort')] + private readonly bool $allowNaturalSort) + { + } + + public function __invoke(RequestEvent $event) + { + Natsort::allowSlowNaturalSort($this->allowNaturalSort); + } +} \ No newline at end of file diff --git a/src/EventListener/ConsoleEnsureWebserverUserListener.php b/src/EventListener/ConsoleEnsureWebserverUserListener.php new file mode 100644 index 00000000..7c119304 --- /dev/null +++ b/src/EventListener/ConsoleEnsureWebserverUserListener.php @@ -0,0 +1,159 @@ +. + */ + +declare(strict_types=1); + + +namespace App\EventListener; + +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; + +/** + * This event listener is called before any console command is executed and should ensure that the webserver + * user is used for all operations (and show a warning if not). This ensures that all files are created with the + * correct permissions. + * If the console is in non-interactive mode, a warning is shown, but the command is still executed. + */ +#[AsEventListener(ConsoleEvents::COMMAND)] +class ConsoleEnsureWebserverUserListener +{ + public function __construct( + #[Autowire('%kernel.project_dir%')] + private readonly string $project_root) + { + } + + public function __invoke(ConsoleCommandEvent $event): void + { + $input = $event->getInput(); + $io = new SymfonyStyle($event->getInput(), $event->getOutput()); + + //Check if we are (not) running as the webserver user + $webserver_user = $this->getWebserverUser(); + $running_user = $this->getRunningUser(); + + //Check if we are trying to run as root + if ($this->isRunningAsRoot()) { + //If the COMPOSER_ALLOW_SUPERUSER environment variable is set, we allow running as root + if ($_SERVER['COMPOSER_ALLOW_SUPERUSER'] ?? false) { + return; + } + + $io->warning('You are running this command as root. This is not recommended, as it can cause permission problems. Please run this command as the webserver user "'. ($webserver_user ?? '??') . '" instead.'); + $io->info('You might have already caused permission problems by running this command as wrong user. If you encounter issues with Part-DB, delete the var/cache directory completely and let it be recreated by Part-DB.'); + if ($input->isInteractive() && !$io->confirm('Do you want to continue?', false)) { + $event->disableCommand(); + } + + return; + } + + if ($webserver_user !== null && $running_user !== null && $webserver_user !== $running_user) { + $io->warning('You are running this command as the user "' . $running_user . '". This is not recommended, as it can cause permission problems. Please run this command as the webserver user "' . $webserver_user . '" instead.'); + $io->info('You might have already caused permission problems by running this command as wrong user. If you encounter issues with Part-DB, delete the var/cache directory completely and let it be recreated by Part-DB.'); + if ($input->isInteractive() && !$io->confirm('Do you want to continue?', false)) { + $event->disableCommand(); + } + + return; + } + } + + /** @noinspection PhpUndefinedFunctionInspection */ + private function isRunningAsRoot(): bool + { + //If we are on windows, we can't run as root + if (PHP_OS_FAMILY === 'Windows') { + return false; + } + + //Try to use the posix extension if available (Linux) + if (function_exists('posix_geteuid')) { + //Check if the current user is root + return posix_geteuid() === 0; + } + + //Otherwise we can't determine the username + return false; + } + + /** + * Determines the username of the user who started the current script if possible. + * Returns null if the username could not be determined. + * @return string|null + * @noinspection PhpUndefinedFunctionInspection + */ + private function getRunningUser(): ?string + { + //Try to use the posix extension if available (Linux) + if (function_exists('posix_geteuid') && function_exists('posix_getpwuid')) { + $id = posix_geteuid(); + + $user = posix_getpwuid($id); + //Try to get the username from the posix extension or return the id + return $user['name'] ?? ("ID: " . $id); + } + + //Otherwise we can't determine the username + return $_SERVER['USERNAME'] ?? $_SERVER['USER'] ?? null; + } + + private function getWebserverUser(): ?string + { + //Determine the webserver user, by checking who owns the uploads/ directory + $path_to_check = $this->project_root . '/uploads/'; + + //Determine the owner of this directory + if (!is_dir($path_to_check)) { + return null; + } + + //If we are on windows we need some special logic + if (PHP_OS_FAMILY === 'Windows') { + //If we have the COM extension available, we can use it to determine the owner + if (extension_loaded('com_dotnet')) { + /** @noinspection PhpUndefinedClassInspection */ + $su = new \COM("ADsSecurityUtility"); // Call interface + //@phpstan-ignore-next-line + $securityInfo = $su->GetSecurityDescriptor($path_to_check, 1, 1); // Call method + return $securityInfo->owner; // Get file owner + } + + //Otherwise we can't determine the owner + return null; + } + + //When we are on a POSIX system, we can use the fileowner function + $owner = fileowner($path_to_check); + + if (function_exists('posix_getpwuid')) { + $user = posix_getpwuid($owner); + //Try to get the username from the posix extension or return the id + return $user['name'] ?? ("ID: " . $owner); + } + + return null; + } + +} \ No newline at end of file diff --git a/src/EventListener/DisallowSearchEngineIndexingRequestListener.php b/src/EventListener/DisallowSearchEngineIndexingRequestListener.php index a1d3304e..b969b6b2 100644 --- a/src/EventListener/DisallowSearchEngineIndexingRequestListener.php +++ b/src/EventListener/DisallowSearchEngineIndexingRequestListener.php @@ -25,7 +25,6 @@ namespace App\EventListener; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; -use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; #[AsEventListener] diff --git a/src/EventSubscriber/LogSystem/EventLoggerSubscriber.php b/src/EventListener/LogSystem/EventLoggerListener.php similarity index 92% rename from src/EventSubscriber/LogSystem/EventLoggerSubscriber.php rename to src/EventListener/LogSystem/EventLoggerListener.php index b3f07a9b..f5029c28 100644 --- a/src/EventSubscriber/LogSystem/EventLoggerSubscriber.php +++ b/src/EventListener/LogSystem/EventLoggerListener.php @@ -20,7 +20,7 @@ declare(strict_types=1); -namespace App\EventSubscriber\LogSystem; +namespace App\EventListener\LogSystem; use App\Entity\Attachments\Attachment; use App\Entity\Base\AbstractDBElement; @@ -38,6 +38,8 @@ use App\Entity\UserSystem\User; use App\Services\LogSystem\EventCommentHelper; 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; @@ -50,7 +52,10 @@ use Symfony\Component\Serializer\SerializerInterface; /** * This event subscriber writes to the event log when entities are changed, removed, created. */ -class EventLoggerSubscriber implements EventSubscriber +#[AsDoctrineListener(event: Events::onFlush)] +#[AsDoctrineListener(event: Events::postPersist)] +#[AsDoctrineListener(event: Events::postFlush)] +class EventLoggerListener { /** * @var array The given fields will not be saved, because they contain sensitive information @@ -71,14 +76,15 @@ class EventLoggerSubscriber implements EventSubscriber ]; 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 @@ -164,6 +170,7 @@ class EventLoggerSubscriber implements EventSubscriber public function hasFieldRestrictions(AbstractDBElement $element): bool { foreach (array_keys(static::FIELD_BLACKLIST) as $class) { + /** @var string $class */ if ($element instanceof $class) { return true; } @@ -178,6 +185,7 @@ class EventLoggerSubscriber implements EventSubscriber 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; } @@ -187,15 +195,6 @@ class EventLoggerSubscriber implements EventSubscriber return true; } - public function getSubscribedEvents(): array - { - return[ - Events::onFlush, - Events::postPersist, - Events::postFlush, - ]; - } - protected function logElementDeleted(AbstractDBElement $entity, EntityManagerInterface $em): void { $log = new ElementDeletedLogEntry($entity); @@ -206,18 +205,19 @@ class EventLoggerSubscriber implements EventSubscriber 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) { @@ -249,9 +249,9 @@ class EventLoggerSubscriber implements EventSubscriber } $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']); @@ -319,7 +319,7 @@ class EventLoggerSubscriber implements EventSubscriber $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/LogSystem/LogDBMigrationSubscriber.php b/src/EventListener/LogSystem/LogDBMigrationListener.php similarity index 92% rename from src/EventSubscriber/LogSystem/LogDBMigrationSubscriber.php rename to src/EventListener/LogSystem/LogDBMigrationListener.php index 07ec8ea4..c8b60e18 100644 --- a/src/EventSubscriber/LogSystem/LogDBMigrationSubscriber.php +++ b/src/EventListener/LogSystem/LogDBMigrationListener.php @@ -20,11 +20,11 @@ declare(strict_types=1); -namespace App\EventSubscriber\LogSystem; +namespace App\EventListener\LogSystem; use App\Entity\LogSystem\DatabaseUpdatedLogEntry; use App\Services\LogSystem\EventLogger; -use Doctrine\Common\EventSubscriber; +use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener; use Doctrine\Migrations\DependencyFactory; use Doctrine\Migrations\Event\MigrationsEventArgs; use Doctrine\Migrations\Events; @@ -32,7 +32,9 @@ use Doctrine\Migrations\Events; /** * This subscriber logs databaseMigrations to Event log. */ -class LogDBMigrationSubscriber implements EventSubscriber +#[AsDoctrineListener(event: Events::onMigrationsMigrated)] +#[AsDoctrineListener(event: Events::onMigrationsMigrating)] +class LogDBMigrationListener { protected ?string $old_version = null; protected ?string $new_version = null; diff --git a/src/EventListener/RegisterSynonymsAsTranslationParametersListener.php b/src/EventListener/RegisterSynonymsAsTranslationParametersListener.php new file mode 100644 index 00000000..5862fa33 --- /dev/null +++ b/src/EventListener/RegisterSynonymsAsTranslationParametersListener.php @@ -0,0 +1,93 @@ +. + */ + +declare(strict_types=1); + + +namespace App\EventListener; + +use App\Services\ElementTypeNameGenerator; +use App\Services\ElementTypes; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Translation\Translator; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\ItemInterface; +use Symfony\Contracts\Cache\TagAwareCacheInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +#[AsEventListener] +readonly class RegisterSynonymsAsTranslationParametersListener +{ + private Translator $translator; + + public function __construct( + #[Autowire(service: 'translator.default')] TranslatorInterface $translator, + private TagAwareCacheInterface $cache, + private ElementTypeNameGenerator $typeNameGenerator) + { + if (!$translator instanceof Translator) { + throw new \RuntimeException('Translator must be an instance of Symfony\Component\Translation\Translator or this listener cannot be used.'); + } + $this->translator = $translator; + } + + public function getSynonymPlaceholders(string $locale): array + { + return $this->cache->get('partdb_synonym_placeholders' . '_' . $locale, function (ItemInterface $item) use ($locale) { + $item->tag('synonyms'); + + + $placeholders = []; + + //Generate a placeholder for each element type + foreach (ElementTypes::cases() as $elementType) { + //Versions with capitalized first letter + $capitalized = ucfirst($elementType->value); //We have only ASCII element type values, so this is sufficient + $placeholders['[' . $capitalized . ']'] = $this->typeNameGenerator->typeLabel($elementType, $locale); + $placeholders['[[' . $capitalized . ']]'] = $this->typeNameGenerator->typeLabelPlural($elementType, $locale); + + //And we have lowercase versions for both + $placeholders['[' . $elementType->value . ']'] = mb_strtolower($this->typeNameGenerator->typeLabel($elementType, $locale)); + $placeholders['[[' . $elementType->value . ']]'] = mb_strtolower($this->typeNameGenerator->typeLabelPlural($elementType, $locale)); + } + + return $placeholders; + }); + } + + public function __invoke(RequestEvent $event): void + { + //If we already added the parameters, skip adding them again + if (isset($this->translator->getGlobalParameters()['@@partdb_synonyms_registered@@'])) { + return; + } + + //Register all placeholders for synonyms + $placeholders = $this->getSynonymPlaceholders($event->getRequest()->getLocale()); + foreach ($placeholders as $key => $value) { + $this->translator->addGlobalParameter($key, $value); + } + + //Register the marker parameter to avoid double registration + $this->translator->addGlobalParameter('@@partdb_synonyms_registered@@', 'registered'); + } +} diff --git a/src/EventSubscriber/LogSystem/LogLogoutEventSubscriber.php b/src/EventSubscriber/LogSystem/LogLogoutEventSubscriber.php index 87d97b1e..7e6d2da7 100644 --- a/src/EventSubscriber/LogSystem/LogLogoutEventSubscriber.php +++ b/src/EventSubscriber/LogSystem/LogLogoutEventSubscriber.php @@ -27,7 +27,6 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use App\Entity\LogSystem\UserLogoutLogEntry; use App\Entity\UserSystem\User; use App\Services\LogSystem\EventLogger; -use Symfony\Component\EventDispatcher\Attribute\AsEventListener; use Symfony\Component\Security\Http\Event\LogoutEvent; /** diff --git a/src/EventSubscriber/LogSystem/SecurityEventLoggerSubscriber.php b/src/EventSubscriber/LogSystem/SecurityEventLoggerSubscriber.php index d9af32c5..7880a41d 100644 --- a/src/EventSubscriber/LogSystem/SecurityEventLoggerSubscriber.php +++ b/src/EventSubscriber/LogSystem/SecurityEventLoggerSubscriber.php @@ -48,7 +48,6 @@ use App\Events\SecurityEvents; use App\Services\LogSystem\EventLogger; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Security\Http\Event\SwitchUserEvent; /** * This subscriber writes entries to log if a security related event happens (e.g. the user changes its password). diff --git a/src/EventSubscriber/RedirectToHttpsSubscriber.php b/src/EventSubscriber/RedirectToHttpsSubscriber.php new file mode 100644 index 00000000..7089109e --- /dev/null +++ b/src/EventSubscriber/RedirectToHttpsSubscriber.php @@ -0,0 +1,72 @@ +. + */ + +declare(strict_types=1); + + +namespace App\EventSubscriber; + +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Http\HttpUtils; + +/** + * The purpose of this event listener is (if enabled) to redirect all requests to https. + */ +final class RedirectToHttpsSubscriber implements EventSubscriberInterface +{ + + public function __construct( + #[Autowire('%env(bool:REDIRECT_TO_HTTPS)%')] + private readonly bool $enabled, + private readonly HttpUtils $httpUtils) + { + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => 'onKernelRequest', + ]; + } + + public function onKernelRequest(RequestEvent $event): void + { + //If the feature is disabled, or we are not the main request, we do nothing + if (!$this->enabled || !$event->isMainRequest()) { + return; + } + + + $request = $event->getRequest(); + + //If the request is already https, we do nothing + if ($request->isSecure()) { + return; + } + + + //Change the request to https + $new_url = str_replace('http://', 'https://' ,$request->getUri()); + $event->setResponse($this->httpUtils->createRedirectResponse($event->getRequest(), $new_url)); + } +} \ No newline at end of file diff --git a/src/EventSubscriber/SwitchUserEventSubscriber.php b/src/EventSubscriber/SwitchUserEventSubscriber.php index a7f2e39c..b68f6b4f 100644 --- a/src/EventSubscriber/SwitchUserEventSubscriber.php +++ b/src/EventSubscriber/SwitchUserEventSubscriber.php @@ -38,7 +38,7 @@ class SwitchUserEventSubscriber implements EventSubscriberInterface { } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ 'security.switch_user' => 'onSwitchUser', diff --git a/src/EventSubscriber/SymfonyDebugToolbarSubscriber.php b/src/EventSubscriber/SymfonyDebugToolbarSubscriber.php deleted file mode 100644 index 6f17e399..00000000 --- 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/PartUniqueIpnSubscriber.php b/src/EventSubscriber/UserSystem/PartUniqueIpnSubscriber.php new file mode 100644 index 00000000..ecc25b4f --- /dev/null +++ b/src/EventSubscriber/UserSystem/PartUniqueIpnSubscriber.php @@ -0,0 +1,97 @@ +ipnSuggestSettings->autoAppendSuffix) { + return; + } + + $em = $args->getObjectManager(); + $uow = $em->getUnitOfWork(); + $meta = $em->getClassMetadata(Part::class); + + // Collect all IPNs already reserved in the current flush (so new entities do not collide with each other) + $reservedIpns = []; + + // Helper to assign a collision-free IPN for a Part entity + $ensureUnique = function (Part $part) use ($em, $uow, $meta, &$reservedIpns) { + $ipn = $part->getIpn(); + if ($ipn === null || $ipn === '') { + return; + } + + // Check against IPNs already reserved in the current flush (except itself) + $originalIpn = $ipn; + $candidate = $originalIpn; + $increment = 1; + + $conflicts = function (string $candidate) use ($em, $part, $reservedIpns) { + // Collision within the current flush session? + if (isset($reservedIpns[$candidate]) && $reservedIpns[$candidate] !== $part) { + return true; + } + // Collision with an existing DB row? + $existing = $em->getRepository(Part::class)->findOneBy(['ipn' => $candidate]); + return $existing !== null && $existing->getId() !== $part->getId(); + }; + + while ($conflicts($candidate)) { + $candidate = $originalIpn . '_' . $increment; + $increment++; + } + + if ($candidate !== $ipn) { + $before = $part->getIpn(); + $part->setIpn($candidate); + + // Recompute the change set so Doctrine writes the change + $uow->recomputeSingleEntityChangeSet($meta, $part); + $reservedIpns[$candidate] = $part; + + // If the old IPN was reserved already, clean it up + if ($before !== null && isset($reservedIpns[$before]) && $reservedIpns[$before] === $part) { + unset($reservedIpns[$before]); + } + } else { + // Candidate unchanged, but reserve it so subsequent entities see it + $reservedIpns[$candidate] = $part; + } + }; + + // 1) Iterate over new entities + foreach ($uow->getScheduledEntityInsertions() as $entity) { + if ($entity instanceof Part) { + $ensureUnique($entity); + } + } + + // 2) Iterate over updates (if IPN changed, ensure uniqueness again) + foreach ($uow->getScheduledEntityUpdates() as $entity) { + if ($entity instanceof Part) { + $ensureUnique($entity); + } + } + } +} diff --git a/src/EventSubscriber/UserSystem/PasswordChangeNeededSubscriber.php b/src/EventSubscriber/UserSystem/PasswordChangeNeededSubscriber.php index 8cf0dfd1..2eb32436 100644 --- a/src/EventSubscriber/UserSystem/PasswordChangeNeededSubscriber.php +++ b/src/EventSubscriber/UserSystem/PasswordChangeNeededSubscriber.php @@ -27,9 +27,7 @@ use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; use Symfony\Component\HttpFoundation\Session\Session; -use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Security\Http\HttpUtils; diff --git a/src/EventSubscriber/UserSystem/SetUserTimezoneSubscriber.php b/src/EventSubscriber/UserSystem/SetUserTimezoneSubscriber.php index 10ecaddf..9964c618 100644 --- a/src/EventSubscriber/UserSystem/SetUserTimezoneSubscriber.php +++ b/src/EventSubscriber/UserSystem/SetUserTimezoneSubscriber.php @@ -23,6 +23,7 @@ declare(strict_types=1); 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 @@ use Symfony\Component\HttpKernel\KernelEvents; */ 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 @@ final class SetUserTimezoneSubscriber implements EventSubscriberInterface } //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 00000000..97abb19f --- /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/Exceptions/TwigModeException.php b/src/Exceptions/TwigModeException.php index adcc86aa..b76d14d3 100644 --- a/src/Exceptions/TwigModeException.php +++ b/src/Exceptions/TwigModeException.php @@ -46,8 +46,23 @@ use Twig\Error\Error; class TwigModeException extends RuntimeException { + private const PROJECT_PATH = __DIR__ . '/../../'; + public function __construct(?Error $previous = null) { parent::__construct($previous->getMessage(), 0, $previous); } + + /** + * Returns the message of this exception, where it is tried to remove any sensitive information (like filepaths). + * @return string + */ + public function getSafeMessage(): string + { + //Resolve project root path + $projectPath = realpath(self::PROJECT_PATH); + + //Remove occurrences of the project path from the message + return str_replace($projectPath, '[Part-DB Root Folder]', $this->getMessage()); + } } diff --git a/src/Form/AdminPages/BaseEntityAdminForm.php b/src/Form/AdminPages/BaseEntityAdminForm.php index 19af4de8..5a4ef5bc 100644 --- a/src/Form/AdminPages/BaseEntityAdminForm.php +++ b/src/Form/AdminPages/BaseEntityAdminForm.php @@ -25,6 +25,7 @@ namespace App\Form\AdminPages; 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; @@ -35,7 +36,6 @@ use App\Form\Type\MasterPictureAttachmentType; use App\Form\Type\RichTextEditorType; use App\Form\Type\StructuralEntityType; use App\Services\LogSystem\EventCommentNeededHelper; -use function get_class; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; @@ -153,7 +153,7 @@ class BaseEntityAdminForm extends AbstractType $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/CategoryAdminForm.php b/src/Form/AdminPages/CategoryAdminForm.php index 10a56646..489649ed 100644 --- a/src/Form/AdminPages/CategoryAdminForm.php +++ b/src/Form/AdminPages/CategoryAdminForm.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Form\AdminPages; use App\Entity\Base\AbstractNamedDBElement; +use App\Form\Part\EDA\EDACategoryInfoType; use App\Form\Type\RichTextEditorType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\TextType; @@ -83,6 +84,17 @@ class CategoryAdminForm extends BaseEntityAdminForm 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), ]); + $builder->add('part_ipn_prefix', TextType::class, [ + 'required' => false, + 'empty_data' => '', + 'label' => 'category.edit.part_ipn_prefix', + 'help' => 'category.edit.part_ipn_prefix.help', + 'attr' => [ + 'placeholder' => 'category.edit.part_ipn_prefix.placeholder', + ], + 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), + ]); + $builder->add('default_description', RichTextEditorType::class, [ 'required' => false, 'empty_data' => '', @@ -104,5 +116,11 @@ class CategoryAdminForm extends BaseEntityAdminForm ], 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), ]); + + //EDA info + $builder->add('eda_info', EDACategoryInfoType::class, [ + 'label' => false, + 'required' => false, + ]); } } diff --git a/src/Form/AdminPages/CurrencyAdminForm.php b/src/Form/AdminPages/CurrencyAdminForm.php index 0fab055d..afcf3c1f 100644 --- a/src/Form/AdminPages/CurrencyAdminForm.php +++ b/src/Form/AdminPages/CurrencyAdminForm.php @@ -22,6 +22,7 @@ declare(strict_types=1); 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 @@ use Symfony\Component\Form\FormBuilderInterface; 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 @@ class CurrencyAdminForm extends BaseEntityAdminForm $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/FootprintAdminForm.php b/src/Form/AdminPages/FootprintAdminForm.php index a01e91bc..f96564d0 100644 --- a/src/Form/AdminPages/FootprintAdminForm.php +++ b/src/Form/AdminPages/FootprintAdminForm.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Form\AdminPages; use App\Entity\Base\AbstractNamedDBElement; +use App\Form\Part\EDA\EDAFootprintInfoType; use App\Form\Type\MasterPictureAttachmentType; use Symfony\Component\Form\FormBuilderInterface; @@ -37,5 +38,11 @@ class FootprintAdminForm extends BaseEntityAdminForm 'filter' => '3d_model', 'entity' => $entity, ]); + + //EDA info + $builder->add('eda_info', EDAFootprintInfoType::class, [ + 'label' => false, + 'required' => false, + ]); } } diff --git a/src/Form/AdminPages/ImportType.php b/src/Form/AdminPages/ImportType.php index 3e87812c..0bd3cea1 100644 --- a/src/Form/AdminPages/ImportType.php +++ b/src/Form/AdminPages/ImportType.php @@ -59,6 +59,8 @@ class ImportType extends AbstractType 'XML' => 'xml', 'CSV' => 'csv', 'YAML' => 'yaml', + 'XLSX' => 'xlsx', + 'XLS' => 'xls', ], 'label' => 'export.format', 'disabled' => $disabled, diff --git a/src/Form/AdminPages/PartCustomStateAdminForm.php b/src/Form/AdminPages/PartCustomStateAdminForm.php new file mode 100644 index 00000000..b8bb2815 --- /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 34b3b27a..43ac0616 100644 --- a/src/Form/AdminPages/SupplierForm.php +++ b/src/Form/AdminPages/SupplierForm.php @@ -22,6 +22,7 @@ declare(strict_types=1); 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 @@ use Symfony\Component\Form\FormBuilderInterface; 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 @@ class SupplierForm extends CompanyForm $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 71c0bedd..eb484a58 100644 --- a/src/Form/AttachmentFormType.php +++ b/src/Form/AttachmentFormType.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Form; +use App\Settings\SystemSettings\AttachmentsSettings; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentType; @@ -48,8 +49,14 @@ use Symfony\Contracts\Translation\TranslatorInterface; class AttachmentFormType extends AbstractType { - public function __construct(protected AttachmentManager $attachment_helper, protected UrlGeneratorInterface $urlGenerator, protected Security $security, protected AttachmentSubmitHandler $submitHandler, protected TranslatorInterface $translator, protected bool $allow_attachments_download, protected string $max_file_size) - { + public function __construct( + protected AttachmentManager $attachment_helper, + protected UrlGeneratorInterface $urlGenerator, + protected Security $security, + protected AttachmentSubmitHandler $submitHandler, + protected TranslatorInterface $translator, + protected AttachmentsSettings $settings, + ) { } public function buildForm(FormBuilderInterface $builder, array $options): void @@ -85,7 +92,8 @@ class AttachmentFormType extends AbstractType 'required' => false, 'attr' => [ 'data-controller' => 'elements--attachment-autocomplete', - 'data-autocomplete' => $this->urlGenerator->generate('typeahead_builtInRessources', ['query' => '__QUERY__']), + 'data-autocomplete' => $this->urlGenerator->generate('typeahead_builtInRessources', + ['query' => '__QUERY__']), //Disable browser autocomplete 'autocomplete' => 'off', ], @@ -99,7 +107,7 @@ class AttachmentFormType extends AbstractType 'required' => false, 'label' => 'attachment.edit.download_url', 'mapped' => false, - 'disabled' => !$this->allow_attachments_download, + 'disabled' => !$this->settings->allowDownloads, ]); $builder->add('file', FileType::class, [ @@ -132,6 +140,12 @@ class AttachmentFormType extends AbstractType } if (!$file instanceof UploadedFile) { + //When no file was uploaded, but a URL was entered, try to determine the attachment name from the URL + if ((trim($attachment->getName()) === '') && ($attachment->getURL() !== null && $attachment->getURL() !== '')) { + $name = basename(parse_url($attachment->getURL(), PHP_URL_PATH)); + $attachment->setName($name); + } + return; } @@ -159,13 +173,37 @@ class AttachmentFormType extends AbstractType } } ); + + //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->settings->downloadByDefault && $this->settings->allowDownloads) { + $builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event): void { + $form = $event->getForm(); + $attachment = $form->getData(); + + if (!$attachment instanceof Attachment && $attachment !== null) { + return; + } + + //If the attachment was not created yet, set the downloadURL checkbox to true + if ($attachment === null || $attachment->getId() === null) { + $checkbox = $form->get('downloadURL'); + //Ensure that the checkbox is not disabled + if ($checkbox->isDisabled()) { + return; + } + //Set the checkbox + $checkbox->setData(true); + } + }); + } } 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 4fa93852..52cd4186 100644 --- a/src/Form/CollectionTypeExtension.php +++ b/src/Form/Extension/CollectionTypeExtension.php @@ -39,7 +39,7 @@ declare(strict_types=1); * 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 65% rename from src/Form/PasswordTypeExtension.php rename to src/Form/Extension/PasswordTypeExtension.php index f97eeb8e..cc0486b0 100644 --- a/src/Form/PasswordTypeExtension.php +++ b/src/Form/Extension/PasswordTypeExtension.php @@ -1,4 +1,25 @@ . + */ + +declare(strict_types=1); + /* * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). * @@ -17,8 +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; @@ -46,9 +66,9 @@ class PasswordTypeExtension extends AbstractTypeExtension $resolver->setAllowedTypes('password_estimator', 'bool'); } - public function finishView(FormView $view, FormInterface $form, array $options) + public function finishView(FormView $view, FormInterface $form, array $options): void { $view->vars['password_estimator'] = $options['password_estimator']; } -} \ No newline at end of file +} diff --git a/src/Form/Extension/SelectTypeOrderExtension.php b/src/Form/Extension/SelectTypeOrderExtension.php new file mode 100644 index 00000000..e8e9a93f --- /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 00000000..fec4c0b3 --- /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' => new TranslatableMessage('password_toggle.hide'), + 'visible_label' => new TranslatableMessage('password_toggle.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 92cb20b6..ff80bd38 100644 --- a/src/Form/Filters/AttachmentFilterType.php +++ b/src/Form/Filters/AttachmentFilterType.php @@ -32,7 +32,7 @@ use App\Entity\Attachments\FootprintAttachment; use App\Entity\Attachments\GroupAttachment; use App\Entity\Attachments\LabelAttachment; use App\Entity\Attachments\PartAttachment; -use App\Entity\Attachments\StorelocationAttachment; +use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Attachments\SupplierAttachment; use App\Entity\Attachments\UserAttachment; use App\Entity\Parts\Manufacturer; @@ -85,7 +85,7 @@ class AttachmentFilterType extends AbstractType 'label_profile.label' => LabelAttachment::class, 'manufacturer.label' => Manufacturer::class, 'measurement_unit.label' => MeasurementUnit::class, - 'storelocation.label' => StorelocationAttachment::class, + 'storelocation.label' => StorageLocationAttachment::class, 'supplier.label' => SupplierAttachment::class, 'user.label' => UserAttachment::class, ] @@ -100,6 +100,15 @@ class AttachmentFilterType extends AbstractType '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/Constraints/TextConstraintType.php b/src/Form/Filters/Constraints/TextConstraintType.php index 492278d2..c1007cf9 100644 --- a/src/Form/Filters/Constraints/TextConstraintType.php +++ b/src/Form/Filters/Constraints/TextConstraintType.php @@ -25,7 +25,6 @@ namespace App\Form\Filters\Constraints; use App\DataTables\Filters\Constraints\TextConstraint; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; -use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\Extension\Core\Type\SearchType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; diff --git a/src/Form/Filters/LogFilterType.php b/src/Form/Filters/LogFilterType.php index 0d8257f4..30abf723 100644 --- a/src/Form/Filters/LogFilterType.php +++ b/src/Form/Filters/LogFilterType.php @@ -23,15 +23,9 @@ declare(strict_types=1); namespace App\Form\Filters; use App\DataTables\Filters\LogFilter; -use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentType; use App\Entity\LogSystem\LogLevel; use App\Entity\LogSystem\LogTargetType; use App\Entity\LogSystem\PartStockChangedLogEntry; -use App\Entity\ProjectSystem\Project; -use App\Entity\ProjectSystem\ProjectBOMEntry; -use App\Entity\LabelSystem\LabelProfile; -use App\Entity\LogSystem\AbstractLogEntry; use App\Entity\LogSystem\CollectionElementDeleted; use App\Entity\LogSystem\DatabaseUpdatedLogEntry; use App\Entity\LogSystem\ElementCreatedLogEntry; @@ -42,21 +36,6 @@ use App\Entity\LogSystem\SecurityEventLogEntry; use App\Entity\LogSystem\UserLoginLogEntry; use App\Entity\LogSystem\UserLogoutLogEntry; use App\Entity\LogSystem\UserNotAllowedLogEntry; -use App\Entity\Parameters\AbstractParameter; -use App\Entity\Parts\Category; -use App\Entity\Parts\Footprint; -use App\Entity\Parts\Manufacturer; -use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\Part; -use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; -use App\Entity\Parts\Supplier; -use App\Entity\PriceInformations\Currency; -use App\Entity\PriceInformations\Orderdetail; -use App\Entity\PriceInformations\Pricedetail; -use App\Entity\UserSystem\Group; -use App\Entity\UserSystem\User; -use App\Form\Filters\Constraints\ChoiceConstraintType; use App\Form\Filters\Constraints\DateTimeConstraintType; use App\Form\Filters\Constraints\EnumConstraintType; use App\Form\Filters\Constraints\InstanceOfConstraintType; @@ -121,7 +100,7 @@ class LogFilterType extends AbstractType ]); $builder->add('user', UserEntityConstraintType::class, [ - 'label' => 'log.user', + 'label' => 'log.user', ]); $builder->add('targetType', EnumConstraintType::class, [ @@ -148,11 +127,15 @@ class LogFilterType extends AbstractType LogTargetType::MEASUREMENT_UNIT => 'measurement_unit.label', 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 0a60dc4c..e101c635 100644 --- a/src/Form/Filters/PartFilterType.php +++ b/src/Form/Filters/PartFilterType.php @@ -22,37 +22,50 @@ declare(strict_types=1); */ namespace App\Form\Filters; +use App\DataTables\Filters\Constraints\Part\BulkImportPartStatusConstraint; use App\DataTables\Filters\Constraints\Part\ParameterConstraint; use App\DataTables\Filters\PartFilter; use App\Entity\Attachments\AttachmentType; +use App\Entity\InfoProviderSystem\BulkImportJobStatus; +use App\Entity\InfoProviderSystem\BulkImportPartStatus; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\Storelocation; +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; use App\Form\Filters\Constraints\TagsConstraintType; use App\Form\Filters\Constraints\TextConstraintType; use App\Form\Filters\Constraints\UserEntityConstraintType; -use Svg\Tag\Text; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; -use Symfony\Component\Form\Extension\Core\Type\DateTimeType; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\ResetType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\SubmitButton; use Symfony\Component\OptionsResolver\OptionsResolver; +use function Symfony\Component\Translation\t; + class PartFilterType extends AbstractType { + public function __construct(private readonly Security $security) + { + } + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ @@ -127,6 +140,11 @@ class PartFilterType extends AbstractType 'entity_class' => MeasurementUnit::class ]); + $builder->add('partCustomState', StructuralEntityConstraintType::class, [ + 'label' => 'part.edit.partCustomState', + 'entity_class' => PartCustomState::class + ]); + $builder->add('lastModified', DateTimeConstraintType::class, [ 'label' => 'lastModified' ]); @@ -178,8 +196,8 @@ class PartFilterType extends AbstractType $builder->add('orderdetailsCount', NumberConstraintType::class, [ 'label' => 'part.filter.orderdetails_count', - 'step' => 1, - 'min' => 0, + 'step' => 1, + 'min' => 0, ]); $builder->add('obsolete', BooleanConstraintType::class, [ @@ -191,7 +209,7 @@ class PartFilterType extends AbstractType */ $builder->add('storelocation', StructuralEntityConstraintType::class, [ 'label' => 'storelocation.label', - 'entity_class' => Storelocation::class + 'entity_class' => StorageLocation::class ]); $builder->add('minAmount', NumberConstraintType::class, [ @@ -271,6 +289,56 @@ class PartFilterType extends AbstractType 'min' => 0, ]); + /************************************************************************** + * Project tab + **************************************************************************/ + if ($this->security->isGranted('read', Project::class)) { + $builder + ->add('project', StructuralEntityConstraintType::class, [ + 'label' => 'project.label', + 'entity_class' => Project::class + ]) + ->add('bomQuantity', NumberConstraintType::class, [ + 'label' => 'project.bom.quantity', + 'min' => 0, + 'step' => "any", + ]) + ->add('bomName', TextConstraintType::class, [ + 'label' => 'project.bom.name', + ]) + ->add('bomComment', TextConstraintType::class, [ + 'label' => 'project.bom.comment', + ]) + ; + + } + + /************************************************************************** + * 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 00000000..85e43e6e --- /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 00000000..24a3cfb4 --- /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 00000000..13e9581e --- /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 00000000..ea70284f --- /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 00000000..cecf62a3 --- /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 6ebe663d..bad3edaa 100644 --- a/src/Form/InfoProviderSystem/ProviderSelectType.php +++ b/src/Form/InfoProviderSystem/ProviderSelectType.php @@ -25,11 +25,12 @@ namespace App\Form\InfoProviderSystem; use App\Services\InfoProviderSystem\ProviderRegistry; use App\Services\InfoProviderSystem\Providers\InfoProviderInterface; -use Hoa\Compiler\Llk\Rule\Choice; 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; +use Symfony\Component\Translation\StaticMessage; class ProviderSelectType extends AbstractType { @@ -43,15 +44,45 @@ class ProviderSelectType extends AbstractType return ChoiceType::class; } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { - $resolver->setDefaults([ - 'choices' => $this->providerRegistry->getActiveProviders(), - 'choice_label' => ChoiceList::label($this, fn (?InfoProviderInterface $choice) => $choice?->getProviderInfo()['name']), - 'choice_value' => ChoiceList::value($this, 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) => new StaticMessage($choice?->getProviderInfo()['name'])); + } + + return static fn ($choice, $key, $value) => new StaticMessage($key); + }); + $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/LabelOptionsType.php b/src/Form/LabelOptionsType.php index 0b15046c..ad458374 100644 --- a/src/Form/LabelOptionsType.php +++ b/src/Form/LabelOptionsType.php @@ -48,7 +48,6 @@ use Symfony\Bundle\SecurityBundle\Security; use App\Entity\LabelSystem\LabelOptions; use App\Form\Type\RichTextEditorType; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\EnumType; use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; diff --git a/src/Form/LabelSystem/LabelDialogType.php b/src/Form/LabelSystem/LabelDialogType.php index 33c79797..d79d01f6 100644 --- a/src/Form/LabelSystem/LabelDialogType.php +++ b/src/Form/LabelSystem/LabelDialogType.php @@ -71,6 +71,32 @@ class LabelDialogType extends AbstractType 'label' => false, 'disabled' => !$this->security->isGranted('@labels.edit_options') || $options['disable_options'], ]); + + $builder->add('save_profile_name', TextType::class, [ + 'required' => false, + 'attr' =>[ + 'placeholder' => 'label_generator.save_profile_name', + ] + ]); + + $builder->add('save_profile', SubmitType::class, [ + 'label' => 'label_generator.save_profile', + 'disabled' => !$this->security->isGranted('@labels.create_profiles'), + 'attr' => [ + 'class' => 'btn btn-outline-success' + ] + ]); + + 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', ]); @@ -81,5 +107,6 @@ class LabelDialogType extends AbstractType parent::configureOptions($resolver); $resolver->setDefault('mapped', false); $resolver->setDefault('disable_options', false); + $resolver->setDefault('profile', null); } } diff --git a/src/Form/LabelSystem/ScanDialogType.php b/src/Form/LabelSystem/ScanDialogType.php index 163ee9c2..13ff8e6f 100644 --- a/src/Form/LabelSystem/ScanDialogType.php +++ b/src/Form/LabelSystem/ScanDialogType.php @@ -41,7 +41,10 @@ declare(strict_types=1); namespace App\Form\LabelSystem; +use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\EnumType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; @@ -53,12 +56,34 @@ class ScanDialogType extends AbstractType { $builder->add('input', TextType::class, [ 'label' => 'scan_dialog.input', + //Do not trim the input, otherwise this damages Format06 barcodes which end with non-printable characters + 'trim' => false, 'attr' => [ 'autofocus' => true, 'id' => 'scan_dialog_input', ], ]); + $builder->add('mode', EnumType::class, [ + 'label' => 'scan_dialog.mode', + 'expanded' => true, + 'class' => BarcodeSourceType::class, + 'required' => false, + 'placeholder' => 'scan_dialog.mode.auto', + 'choice_label' => fn (?BarcodeSourceType $enum) => match($enum) { + null => 'scan_dialog.mode.auto', + BarcodeSourceType::INTERNAL => 'scan_dialog.mode.internal', + BarcodeSourceType::IPN => 'scan_dialog.mode.ipn', + BarcodeSourceType::USER_DEFINED => 'scan_dialog.mode.user', + BarcodeSourceType::EIGP114 => 'scan_dialog.mode.eigp' + }, + ]); + + $builder->add('info_mode', CheckboxType::class, [ + 'label' => 'scan_dialog.info_mode', + 'required' => false, + ]); + $builder->add('submit', SubmitType::class, [ 'label' => 'scan_dialog.submit', ]); diff --git a/src/Form/ParameterType.php b/src/Form/ParameterType.php index 66e664be..4c2174ae 100644 --- a/src/Form/ParameterType.php +++ b/src/Form/ParameterType.php @@ -50,9 +50,10 @@ use App\Entity\Parameters\FootprintParameter; use App\Entity\Parameters\GroupParameter; use App\Entity\Parameters\ManufacturerParameter; use App\Entity\Parameters\PartParameter; -use App\Entity\Parameters\StorelocationParameter; +use App\Entity\Parameters\StorageLocationParameter; use App\Entity\Parameters\SupplierParameter; use App\Entity\Parts\MeasurementUnit; +use App\Form\Type\ExponentialNumberType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\Extension\Core\Type\TextType; @@ -93,7 +94,7 @@ class ParameterType extends AbstractType ], ]); - $builder->add('value_max', NumberType::class, [ + $builder->add('value_max', ExponentialNumberType::class, [ 'label' => false, 'required' => false, 'html5' => true, @@ -101,10 +102,10 @@ class ParameterType extends AbstractType 'step' => 'any', 'placeholder' => 'parameters.max.placeholder', 'class' => 'form-control-sm', - 'style' => 'max-width: 12ch;', + 'style' => 'max-width: 25ch;', ], ]); - $builder->add('value_min', NumberType::class, [ + $builder->add('value_min', ExponentialNumberType::class, [ 'label' => false, 'required' => false, 'html5' => true, @@ -112,10 +113,10 @@ class ParameterType extends AbstractType 'step' => 'any', 'placeholder' => 'parameters.min.placeholder', 'class' => 'form-control-sm', - 'style' => 'max-width: 12ch;', + 'style' => 'max-width: 25ch;', ], ]); - $builder->add('value_typical', NumberType::class, [ + $builder->add('value_typical', ExponentialNumberType::class, [ 'label' => false, 'required' => false, 'html5' => true, @@ -123,7 +124,7 @@ class ParameterType extends AbstractType 'step' => 'any', 'placeholder' => 'parameters.typical.placeholder', 'class' => 'form-control-sm', - 'style' => 'max-width: 12ch;', + 'style' => 'max-width: 25ch;', ], ]); $builder->add('unit', TextType::class, [ @@ -163,7 +164,7 @@ class ParameterType extends AbstractType GroupParameter::class => 'group', ManufacturerParameter::class => 'manufacturer', MeasurementUnit::class => 'measurement_unit', - StorelocationParameter::class => 'storelocation', + StorageLocationParameter::class => 'storelocation', SupplierParameter::class => 'supplier', ]; diff --git a/src/Form/Part/EDA/EDACategoryInfoType.php b/src/Form/Part/EDA/EDACategoryInfoType.php new file mode 100644 index 00000000..f45bd697 --- /dev/null +++ b/src/Form/Part/EDA/EDACategoryInfoType.php @@ -0,0 +1,87 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Part\EDA; + +use App\Entity\EDA\EDACategoryInfo; +use App\Form\Type\TriStateCheckboxType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +use function Symfony\Component\Translation\t; + +class EDACategoryInfoType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('reference_prefix', TextType::class, [ + 'label' => 'eda_info.reference_prefix', + 'attr' => [ + 'placeholder' => t('eda_info.reference_prefix.placeholder'), + ] + ] + ) + ->add('visibility', TriStateCheckboxType::class, [ + 'help' => 'eda_info.visibility.help', + 'label' => 'eda_info.visibility', + ]) + ->add('exclude_from_bom', TriStateCheckboxType::class, [ + 'label' => 'eda_info.exclude_from_bom', + 'label_attr' => [ + 'class' => 'checkbox-inline' + ] + ]) + ->add('exclude_from_board', TriStateCheckboxType::class, [ + 'label' => 'eda_info.exclude_from_board', + 'label_attr' => [ + 'class' => 'checkbox-inline' + ] + ]) + ->add('exclude_from_sim', TriStateCheckboxType::class, [ + 'label' => 'eda_info.exclude_from_sim', + 'label_attr' => [ + 'class' => 'checkbox-inline' + ] + ]) + ->add('kicad_symbol', KicadFieldAutocompleteType::class, [ + 'label' => 'eda_info.kicad_symbol', + 'type' => KicadFieldAutocompleteType::TYPE_SYMBOL, + 'attr' => [ + 'placeholder' => t('eda_info.kicad_symbol.placeholder'), + ] + ]) + ; + + + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => EDACategoryInfo::class, + ]); + } +} \ No newline at end of file diff --git a/src/Form/Part/EDA/EDAFootprintInfoType.php b/src/Form/Part/EDA/EDAFootprintInfoType.php new file mode 100644 index 00000000..bdfa346c --- /dev/null +++ b/src/Form/Part/EDA/EDAFootprintInfoType.php @@ -0,0 +1,55 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Part\EDA; + +use App\Entity\EDA\EDAFootprintInfo; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +use function Symfony\Component\Translation\t; + +class EDAFootprintInfoType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('kicad_footprint', KicadFieldAutocompleteType::class, [ + 'type' => KicadFieldAutocompleteType::TYPE_FOOTPRINT, + 'label' => 'eda_info.kicad_footprint', + 'attr' => [ + 'placeholder' => t('eda_info.kicad_footprint.placeholder'), + ] + ]); + + + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => EDAFootprintInfo::class, + ]); + } +} \ No newline at end of file diff --git a/src/Form/Part/EDA/EDAPartInfoType.php b/src/Form/Part/EDA/EDAPartInfoType.php new file mode 100644 index 00000000..e8cac681 --- /dev/null +++ b/src/Form/Part/EDA/EDAPartInfoType.php @@ -0,0 +1,97 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Part\EDA; + +use App\Entity\EDA\EDAPartInfo; +use App\Form\Type\TriStateCheckboxType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +use function Symfony\Component\Translation\t; + +class EDAPartInfoType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('reference_prefix', TextType::class, [ + 'label' => 'eda_info.reference_prefix', + 'attr' => [ + 'placeholder' => t('eda_info.reference_prefix.placeholder'), + ] + ] + ) + ->add('value', TextType::class, [ + 'label' => 'eda_info.value', + 'attr' => [ + 'placeholder' => t('eda_info.value.placeholder'), + ] + ]) + ->add('visibility', TriStateCheckboxType::class, [ + 'help' => 'eda_info.visibility.help', + 'label' => 'eda_info.visibility', + ]) + ->add('exclude_from_bom', TriStateCheckboxType::class, [ + 'label' => 'eda_info.exclude_from_bom', + 'label_attr' => [ + 'class' => 'checkbox-inline' + ] + ]) + ->add('exclude_from_board', TriStateCheckboxType::class, [ + 'label' => 'eda_info.exclude_from_board', + 'label_attr' => [ + 'class' => 'checkbox-inline' + ] + ]) + ->add('exclude_from_sim', TriStateCheckboxType::class, [ + 'label' => 'eda_info.exclude_from_sim', + 'label_attr' => [ + 'class' => 'checkbox-inline' + ] + ]) + ->add('kicad_symbol', KicadFieldAutocompleteType::class, [ + 'label' => 'eda_info.kicad_symbol', + 'type' => KicadFieldAutocompleteType::TYPE_SYMBOL, + 'attr' => [ + 'placeholder' => t('eda_info.kicad_symbol.placeholder'), + ] + ]) + ->add('kicad_footprint', KicadFieldAutocompleteType::class, [ + 'label' => 'eda_info.kicad_footprint', + 'type' => KicadFieldAutocompleteType::TYPE_FOOTPRINT, + 'attr' => [ + 'placeholder' => t('eda_info.kicad_footprint.placeholder'), + ] + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => EDAPartInfo::class, + ]); + } +} \ No newline at end of file diff --git a/src/Form/Part/EDA/KicadFieldAutocompleteType.php b/src/Form/Part/EDA/KicadFieldAutocompleteType.php new file mode 100644 index 00000000..50de81d0 --- /dev/null +++ b/src/Form/Part/EDA/KicadFieldAutocompleteType.php @@ -0,0 +1,61 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Part\EDA; + +use App\Form\Type\StaticFileAutocompleteType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * This is a specialized version of the StaticFileAutocompleteType, which loads the different types of Kicad lists. + */ +class KicadFieldAutocompleteType extends AbstractType +{ + public const TYPE_FOOTPRINT = 'footprint'; + public const TYPE_SYMBOL = 'symbol'; + + //Do not use a leading slash here! otherwise it will not work under prefixed reverse proxies + public const FOOTPRINT_PATH = 'kicad/footprints.txt'; + public const SYMBOL_PATH = 'kicad/symbols.txt'; + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setRequired('type'); + $resolver->setAllowedValues('type', [self::TYPE_SYMBOL, self::TYPE_FOOTPRINT]); + + $resolver->setDefaults([ + 'file' => fn(Options $options) => match ($options['type']) { + self::TYPE_FOOTPRINT => self::FOOTPRINT_PATH, + self::TYPE_SYMBOL => self::SYMBOL_PATH, + default => throw new \InvalidArgumentException('Invalid type'), + } + ]); + } + + public function getParent(): string + { + return StaticFileAutocompleteType::class; + } +} \ No newline at end of file diff --git a/src/Form/Part/PartAssociationType.php b/src/Form/Part/PartAssociationType.php new file mode 100644 index 00000000..bf9ec4f9 --- /dev/null +++ b/src/Form/Part/PartAssociationType.php @@ -0,0 +1,73 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Part; + +use App\Entity\Parts\AssociationType; +use App\Entity\Parts\PartAssociation; +use App\Form\Type\PartSelectType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\EnumType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class PartAssociationType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('other', PartSelectType::class, [ + 'label' => 'part_association.edit.other_part', + ]) + ->add('type', EnumType::class, [ + 'class' => AssociationType::class, + 'label' => 'part_association.edit.type', + 'choice_label' => fn(AssociationType $type) => $type->getTranslationKey(), + 'help' => 'part_association.edit.type.help', + 'attr' => [ + 'data-pages--association-edit-type-select-target' => 'select' + ] + ]) + ->add('other_type', TextType::class, [ + 'required' => false, + 'label' => 'part_association.edit.other_type', + 'row_attr' => [ + 'data-pages--association-edit-type-select-target' => 'display' + ] + ]) + ->add('comment', TextType::class, [ + 'required' => false, + 'label' => 'part_association.edit.comment' + ]) + ; + + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => PartAssociation::class, + ]); + } +} \ No newline at end of file diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php index b15ec29f..b8276589 100644 --- a/src/Form/Part/PartBaseType.php +++ b/src/Form/Part/PartBaseType.php @@ -22,27 +22,30 @@ declare(strict_types=1); namespace App\Form\Part; -use App\Entity\Parts\ManufacturingStatus; -use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; -use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Attachments\PartAttachment; use App\Entity\Parameters\PartParameter; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; +use App\Entity\Parts\ManufacturingStatus; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; +use App\Entity\Parts\PartCustomState; use App\Entity\PriceInformations\Orderdetail; use App\Form\AttachmentFormType; use App\Form\ParameterType; +use App\Form\Part\EDA\EDAPartInfoType; use App\Form\Type\MasterPictureAttachmentType; use App\Form\Type\RichTextEditorType; use App\Form\Type\SIUnitType; use App\Form\Type\StructuralEntityType; +use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; use App\Services\LogSystem\EventCommentNeededHelper; +use App\Services\LogSystem\EventCommentType; +use App\Settings\MiscSettings\IpnSuggestSettings; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\Extension\Core\Type\EnumType; use Symfony\Component\Form\Extension\Core\Type\ResetType; @@ -52,12 +55,15 @@ use Symfony\Component\Form\Extension\Core\Type\UrlType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Contracts\Translation\TranslatorInterface; class PartBaseType extends AbstractType { - public function __construct(protected Security $security, protected UrlGeneratorInterface $urlGenerator, protected EventCommentNeededHelper $event_comment_needed_helper) - { + public function __construct( + protected Security $security, + protected UrlGeneratorInterface $urlGenerator, + protected EventCommentNeededHelper $event_comment_needed_helper, + protected IpnSuggestSettings $ipnSuggestSettings, + ) { } public function buildForm(FormBuilderInterface $builder, array $options): void @@ -69,6 +75,39 @@ class PartBaseType extends AbstractType /** @var PartDetailDTO|null $dto */ $dto = $options['info_provider_dto']; + $descriptionAttr = [ + 'placeholder' => 'part.edit.description.placeholder', + 'rows' => 2, + ]; + + if ($this->ipnSuggestSettings->useDuplicateDescription) { + // Only add attribute when duplicate description feature is enabled + $descriptionAttr['data-ipn-suggestion'] = 'descriptionField'; + } + + $ipnAttr = [ + 'class' => 'ipn-suggestion-field', + 'data-elements--ipn-suggestion-target' => 'input', + 'autocomplete' => 'off', + ]; + + if ($this->ipnSuggestSettings->regex !== null && $this->ipnSuggestSettings->regex !== '') { + $ipnAttr['pattern'] = $this->ipnSuggestSettings->regex; + $ipnAttr['placeholder'] = $this->ipnSuggestSettings->regex; + $ipnAttr['title'] = $this->ipnSuggestSettings->regexHelp; + } + + $ipnOptions = [ + 'required' => false, + 'empty_data' => null, + 'label' => 'part.edit.ipn', + 'attr' => $ipnAttr, + ]; + + if (isset($ipnAttr['pattern']) && $this->ipnSuggestSettings->regexHelp !== null && $this->ipnSuggestSettings->regexHelp !== '') { + $ipnOptions['help'] = $this->ipnSuggestSettings->regexHelp; + } + //Common section $builder ->add('name', TextType::class, [ @@ -83,10 +122,7 @@ class PartBaseType extends AbstractType 'empty_data' => '', 'label' => 'part.edit.description', 'mode' => 'markdown-single_line', - 'attr' => [ - 'placeholder' => 'part.edit.description.placeholder', - 'rows' => 2, - ], + 'attr' => $descriptionAttr, ]) ->add('minAmount', SIUnitType::class, [ 'attr' => [ @@ -102,6 +138,11 @@ class PartBaseType extends AbstractType 'dto_value' => $dto?->category, 'label' => 'part.edit.category', 'disable_not_selectable' => true, + //Do not require category for new parts, so that the user must select the category by hand and cannot forget it (the requirement is handled by the constraint in the entity) + 'required' => !$new_part, + 'attr' => [ + 'data-ipn-suggestion' => 'categoryField', + ] ]) ->add('footprint', StructuralEntityType::class, [ 'class' => Footprint::class, @@ -169,11 +210,13 @@ class PartBaseType extends AbstractType 'disable_not_selectable' => true, 'label' => 'part.edit.partUnit', ]) - ->add('ipn', TextType::class, [ + ->add('partCustomState', StructuralEntityType::class, [ + 'class' => PartCustomState::class, 'required' => false, - 'empty_data' => null, - 'label' => 'part.edit.ipn', - ]); + 'disable_not_selectable' => true, + 'label' => 'part.edit.partCustomState', + ]) + ->add('ipn', TextType::class, $ipnOptions); //Comment section $builder->add('comment', RichTextEditorType::class, [ @@ -245,10 +288,26 @@ class PartBaseType extends AbstractType ], ]); + //Part associations + $builder->add('associated_parts_as_owner', CollectionType::class, [ + 'entry_type' => PartAssociationType::class, + 'allow_add' => true, + 'allow_delete' => true, + 'reindex_enable' => true, + 'label' => false, + 'by_reference' => false, + ]); + + //EDA info + $builder->add('eda_info', EDAPartInfoType::class, [ + 'label' => false, + 'required' => false, + ]); + $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/Part/PartLotType.php b/src/Form/Part/PartLotType.php index 707caf28..7d545340 100644 --- a/src/Form/Part/PartLotType.php +++ b/src/Form/Part/PartLotType.php @@ -25,7 +25,7 @@ namespace App\Form\Part; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Form\Type\SIUnitType; use App\Form\Type\StructuralEntityType; use App\Form\Type\UserSelectType; @@ -54,7 +54,7 @@ class PartLotType extends AbstractType ]); $builder->add('storage_location', StructuralEntityType::class, [ - 'class' => Storelocation::class, + 'class' => StorageLocation::class, 'label' => 'part_lot.edit.location', 'required' => false, 'disable_not_selectable' => true, @@ -80,7 +80,7 @@ class PartLotType extends AbstractType 'required' => false, ]); - $builder->add('expirationDate', DateType::class, [ + $builder->add('expiration_date', DateType::class, [ 'label' => 'part_lot.edit.expiration_date', 'attr' => [], 'widget' => 'single_text', @@ -102,6 +102,14 @@ class PartLotType extends AbstractType 'required' => false, 'help' => 'part_lot.owner.help', ]); + + $builder->add('user_barcode', TextType::class, [ + 'label' => 'part_lot.edit.user_barcode', + 'help' => 'part_lot.edit.vendor_barcode.help', + 'required' => false, + //Do not remove whitespace chars on the beginning and end of the string + 'trim' => false, + ]); } public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Form/Part/PricedetailType.php b/src/Form/Part/PricedetailType.php index c8df4c71..cabb112d 100644 --- a/src/Form/Part/PricedetailType.php +++ b/src/Form/Part/PricedetailType.php @@ -27,12 +27,18 @@ use App\Entity\PriceInformations\Pricedetail; use App\Form\Type\BigDecimalNumberType; use App\Form\Type\CurrencyEntityType; use App\Form\Type\SIUnitType; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; class PricedetailType extends AbstractType { + + public function __construct(private readonly Security $security) + { + } + public function buildForm(FormBuilderInterface $builder, array $options): void { //No labels needed, we define translation in templates @@ -63,6 +69,7 @@ class PricedetailType extends AbstractType 'required' => false, 'label' => false, 'short' => true, + 'allow_add' => $this->security->isGranted('@currencies.create'), ]); } diff --git a/src/Form/Permissions/PermissionsMapper.php b/src/Form/Permissions/PermissionsMapper.php index 2163b26f..d4b937bc 100644 --- a/src/Form/Permissions/PermissionsMapper.php +++ b/src/Form/Permissions/PermissionsMapper.php @@ -47,7 +47,7 @@ final class PermissionsMapper implements DataMapperInterface * @param mixed $viewData View data of the compound form being initialized * @param Traversable $forms A list of {@link FormInterface} instances */ - public function mapDataToForms($viewData, \Traversable $forms): void + public function mapDataToForms(mixed $viewData, \Traversable $forms): void { foreach ($forms as $form) { if ($this->inherit) { @@ -94,7 +94,7 @@ final class PermissionsMapper implements DataMapperInterface * @param mixed $viewData The compound form's view data that get mapped * its children model data */ - public function mapFormsToData(\Traversable $forms, &$viewData): void + public function mapFormsToData(\Traversable $forms, mixed &$viewData): void { if ($this->inherit) { throw new RuntimeException('The permission type is readonly when it is showing read only data!'); diff --git a/src/Form/Permissions/PermissionsType.php b/src/Form/Permissions/PermissionsType.php index b0c7ba9d..86fdbc2c 100644 --- a/src/Form/Permissions/PermissionsType.php +++ b/src/Form/Permissions/PermissionsType.php @@ -45,6 +45,7 @@ class PermissionsType extends AbstractType $resolver->setDefaults([ 'show_legend' => true, 'show_presets' => false, + 'show_dependency_notice' => static fn(Options $options) => !$options['disabled'], 'constraints' => static function (Options $options) { if (!$options['disabled']) { return [new NoLockout()]; @@ -60,6 +61,7 @@ class PermissionsType extends AbstractType { $view->vars['show_legend'] = $options['show_legend']; $view->vars['show_presets'] = $options['show_presets']; + $view->vars['show_dependency_notice'] = $options['show_dependency_notice']; } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Form/ProjectSystem/ProjectAddPartsType.php b/src/Form/ProjectSystem/ProjectAddPartsType.php index f89f3567..61f72c41 100644 --- a/src/Form/ProjectSystem/ProjectAddPartsType.php +++ b/src/Form/ProjectSystem/ProjectAddPartsType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\ProjectSystem; use App\Entity\ProjectSystem\Project; @@ -35,7 +37,7 @@ use Symfony\Component\Validator\Constraints\NotNull; class ProjectAddPartsType extends AbstractType { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('project', StructuralEntityType::class, [ 'class' => Project::class, @@ -49,13 +51,15 @@ class ProjectAddPartsType extends AbstractType $builder->add('bom_entries', ProjectBOMEntryCollectionType::class, [ 'entry_options' => [ 'constraints' => [ - new UniqueEntity(fields: ['part', 'project'], entityClass: ProjectBOMEntry::class, message: 'project.bom_entry.part_already_in_bom'), - new UniqueEntity(fields: ['name', 'project'], entityClass: ProjectBOMEntry::class, message: 'project.bom_entry.name_already_in_bom', ignoreNull: true), + new UniqueEntity(fields: ['part', 'project'], message: 'project.bom_entry.part_already_in_bom', + entityClass: ProjectBOMEntry::class), + new UniqueEntity(fields: ['name', 'project'], message: 'project.bom_entry.name_already_in_bom', + entityClass: ProjectBOMEntry::class, ignoreNull: true), ] ], 'constraints' => [ - new UniqueObjectCollection(fields: ['part'], message: 'project.bom_entry.part_already_in_bom'), - new UniqueObjectCollection(fields: ['name'], message: 'project.bom_entry.name_already_in_bom'), + new UniqueObjectCollection(message: 'project.bom_entry.part_already_in_bom', fields: ['part']), + new UniqueObjectCollection(message: 'project.bom_entry.name_already_in_bom', fields: ['name']), ] ]); $builder->add('submit', SubmitType::class, ['label' => 'save']); @@ -73,7 +77,7 @@ class ProjectAddPartsType extends AbstractType }); } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'project' => null, @@ -81,4 +85,4 @@ class ProjectAddPartsType extends AbstractType $resolver->setAllowedTypes('project', ['null', Project::class]); } -} \ No newline at end of file +} diff --git a/src/Form/ProjectSystem/ProjectBuildType.php b/src/Form/ProjectSystem/ProjectBuildType.php index 3fdc491f..2b7b52e2 100644 --- a/src/Form/ProjectSystem/ProjectBuildType.php +++ b/src/Form/ProjectSystem/ProjectBuildType.php @@ -155,7 +155,7 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface $matches = []; if (preg_match('/^lot_(\d+)$/', $key, $matches)) { $lot_id = (int) $matches[1]; - $data->setLotWithdrawAmount($lot_id, $form->getData()); + $data->setLotWithdrawAmount($lot_id, (float) $form->getData()); } } diff --git a/src/Form/Settings/LanguageMenuEntriesType.php b/src/Form/Settings/LanguageMenuEntriesType.php new file mode 100644 index 00000000..9bc2e850 --- /dev/null +++ b/src/Form/Settings/LanguageMenuEntriesType.php @@ -0,0 +1,56 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Settings; + +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\LanguageType; +use Symfony\Component\Intl\Languages; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class LanguageMenuEntriesType extends AbstractType +{ + public function __construct(#[Autowire(param: 'partdb.locale_menu')] private readonly array $preferred_languages) + { + + } + + public function getParent(): string + { + return LanguageType::class; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $choices = []; + foreach ($this->preferred_languages as $lang_code) { + $choices[Languages::getName($lang_code)] = $lang_code; + } + + $resolver->setDefaults([ + 'choice_loader' => null, + 'choices' => $choices, + ]); + } +} diff --git a/src/Form/Settings/TypeSynonymRowType.php b/src/Form/Settings/TypeSynonymRowType.php new file mode 100644 index 00000000..f3b8f0b6 --- /dev/null +++ b/src/Form/Settings/TypeSynonymRowType.php @@ -0,0 +1,150 @@ +. + */ + +declare(strict_types=1); + +namespace App\Form\Settings; + +use App\Services\ElementTypes; +use App\Settings\SystemSettings\LocalizationSettings; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\EnumType; +use Symfony\Component\Form\Extension\Core\Type\LocaleType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Intl\Locales; +use Symfony\Component\Translation\StaticMessage; +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * A single translation row: data source + language + translations (singular/plural). + */ +class TypeSynonymRowType extends AbstractType +{ + + private const PREFERRED_TYPES = [ + ElementTypes::CATEGORY, + ElementTypes::STORAGE_LOCATION, + ElementTypes::FOOTPRINT, + ElementTypes::MANUFACTURER, + ElementTypes::SUPPLIER, + ElementTypes::PROJECT, + ]; + + public function __construct( + private readonly LocalizationSettings $localizationSettings, + private readonly TranslatorInterface $translator, + #[Autowire(param: 'partdb.locale_menu')] private readonly array $preferredLanguagesParam, + ) { + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('dataSource', EnumType::class, [ + 'class' => ElementTypes::class, + 'label' => false, + 'required' => true, + 'constraints' => [ + new Assert\NotBlank(), + ], + 'choice_label' => function (ElementTypes $choice) { + return new StaticMessage( + $this->translator->trans($choice->getDefaultLabelKey()) . ' (' . $this->translator->trans($choice->getDefaultPluralLabelKey()) . ')' + ); + }, + 'row_attr' => ['class' => 'mb-0'], + 'attr' => ['class' => 'form-select-sm'], + 'preferred_choices' => self::PREFERRED_TYPES + ]) + ->add('locale', LocaleType::class, [ + 'label' => false, + 'required' => true, + // Restrict to languages configured in the language menu: disable ChoiceLoader and provide explicit choices + 'choice_loader' => null, + 'choices' => $this->buildLocaleChoices(true), + 'preferred_choices' => $this->getPreferredLocales(), + 'constraints' => [ + new Assert\NotBlank(), + ], + 'row_attr' => ['class' => 'mb-0'], + 'attr' => ['class' => 'form-select-sm'] + ]) + ->add('translation_singular', TextType::class, [ + 'label' => false, + 'required' => true, + 'empty_data' => '', + 'constraints' => [ + new Assert\NotBlank(), + ], + 'row_attr' => ['class' => 'mb-0'], + 'attr' => ['class' => 'form-select-sm'] + ]) + ->add('translation_plural', TextType::class, [ + 'label' => false, + 'required' => true, + 'empty_data' => '', + 'constraints' => [ + new Assert\NotBlank(), + ], + 'row_attr' => ['class' => 'mb-0'], + 'attr' => ['class' => 'form-select-sm'] + ]); + } + + + /** + * Returns only locales configured in the language menu (settings) or falls back to the parameter. + * Format: ['German (DE)' => 'de', ...] + */ + private function buildLocaleChoices(bool $returnPossible = false): array + { + $locales = $this->getPreferredLocales(); + + if ($returnPossible) { + $locales = $this->getPossibleLocales(); + } + + $choices = []; + foreach ($locales as $code) { + $label = Locales::getName($code); + $choices[$label . ' (' . strtoupper($code) . ')'] = $code; + } + return $choices; + } + + /** + * Source of allowed locales: + * 1) LocalizationSettings->languageMenuEntries (if set) + * 2) Fallback: parameter partdb.locale_menu + */ + private function getPreferredLocales(): array + { + $fromSettings = $this->localizationSettings->languageMenuEntries ?? []; + return !empty($fromSettings) ? array_values($fromSettings) : array_values($this->preferredLanguagesParam); + } + + private function getPossibleLocales(): array + { + return array_values($this->preferredLanguagesParam); + } +} diff --git a/src/Form/Settings/TypeSynonymsCollectionType.php b/src/Form/Settings/TypeSynonymsCollectionType.php new file mode 100644 index 00000000..4756930a --- /dev/null +++ b/src/Form/Settings/TypeSynonymsCollectionType.php @@ -0,0 +1,223 @@ +. + */ + +declare(strict_types=1); + +namespace App\Form\Settings; + +use App\Services\ElementTypes; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\CallbackTransformer; +use Symfony\Component\Form\Extension\Core\Type\CollectionType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Intl\Locales; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Flat collection of translation rows. + * View data: list [{dataSource, locale, translation_singular, translation_plural}, ...] + * Model data: same structure (list). Optionally expands a nested map to a list. + */ +class TypeSynonymsCollectionType extends AbstractType +{ + public function __construct(private readonly TranslatorInterface $translator) + { + } + + private function flattenStructure(array $modelValue): array + { + //If the model is already flattened, return as is + if (array_is_list($modelValue)) { + return $modelValue; + } + + $out = []; + foreach ($modelValue as $dataSource => $locales) { + if (!is_array($locales)) { + continue; + } + foreach ($locales as $locale => $translations) { + if (!is_array($translations)) { + continue; + } + $out[] = [ + //Convert string to enum value + 'dataSource' => ElementTypes::from($dataSource), + 'locale' => $locale, + 'translation_singular' => $translations['singular'] ?? '', + 'translation_plural' => $translations['plural'] ?? '', + ]; + } + } + return $out; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event): void { + //Flatten the structure + $data = $event->getData(); + $event->setData($this->flattenStructure($data)); + }); + + $builder->addModelTransformer(new CallbackTransformer( + // Model -> View + $this->flattenStructure(...), + // View -> Model (keep list; let existing behavior unchanged) + function (array $viewValue) { + //Turn our flat list back into the structured array + + $out = []; + + foreach ($viewValue as $row) { + if (!is_array($row)) { + continue; + } + $dataSource = $row['dataSource'] ?? null; + $locale = $row['locale'] ?? null; + $translation_singular = $row['translation_singular'] ?? null; + $translation_plural = $row['translation_plural'] ?? null; + + if ($dataSource === null || + !is_string($locale) || $locale === '' + ) { + continue; + } + + $out[$dataSource->value][$locale] = [ + 'singular' => is_string($translation_singular) ? $translation_singular : '', + 'plural' => is_string($translation_plural) ? $translation_plural : '', + ]; + } + + return $out; + } + )); + + // Validation and normalization (duplicates + sorting) during SUBMIT + $builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event): void { + $form = $event->getForm(); + $rows = $event->getData(); + + if (!is_array($rows)) { + return; + } + + // Duplicate check: (dataSource, locale) must be unique + $seen = []; + $hasDuplicate = false; + + foreach ($rows as $idx => $row) { + if (!is_array($row)) { + continue; + } + $ds = $row['dataSource'] ?? null; + $loc = $row['locale'] ?? null; + + if ($ds !== null && is_string($loc) && $loc !== '') { + $key = $ds->value . '|' . $loc; + if (isset($seen[$key])) { + $hasDuplicate = true; + + if ($form->has((string)$idx)) { + $child = $form->get((string)$idx); + + if ($child->has('dataSource')) { + $child->get('dataSource')->addError( + new FormError($this->translator->trans( + 'settings.synonyms.type_synonyms.collection_type.duplicate', + [], 'validators' + )) + ); + } + if ($child->has('locale')) { + $child->get('locale')->addError( + new FormError($this->translator->trans( + 'settings.synonyms.type_synonyms.collection_type.duplicate', + [], 'validators' + )) + ); + } + } + } else { + $seen[$key] = true; + } + } + } + + if ($hasDuplicate) { + return; + } + + // Overall sort: first by dataSource key, then by localized language name + $sortable = $rows; + + usort($sortable, static function ($a, $b) { + $aDs = $a['dataSource']->value ?? ''; + $bDs = $b['dataSource']->value ?? ''; + + $cmpDs = strcasecmp($aDs, $bDs); + if ($cmpDs !== 0) { + return $cmpDs; + } + + $aLoc = (string)($a['locale'] ?? ''); + $bLoc = (string)($b['locale'] ?? ''); + + $aName = Locales::getName($aLoc); + $bName = Locales::getName($bLoc); + + return strcasecmp($aName, $bName); + }); + + $event->setData($sortable); + }); + } + + public function configureOptions(OptionsResolver $resolver): void + { + + // Defaults for the collection and entry type + $resolver->setDefaults([ + 'entry_type' => TypeSynonymRowType::class, + 'allow_add' => true, + 'allow_delete' => true, + 'by_reference' => false, + 'required' => false, + 'prototype' => true, + 'empty_data' => [], + 'entry_options' => ['label' => false], + ]); + } + + public function getParent(): ?string + { + return CollectionType::class; + } + + public function getBlockPrefix(): string + { + return 'type_synonyms_collection'; + } +} diff --git a/src/Form/TFAGoogleSettingsType.php b/src/Form/TFAGoogleSettingsType.php index e00ba494..7917f705 100644 --- a/src/Form/TFAGoogleSettingsType.php +++ b/src/Form/TFAGoogleSettingsType.php @@ -53,6 +53,7 @@ class TFAGoogleSettingsType extends AbstractType 'google_confirmation', TextType::class, [ + 'label' => 'tfa.check.code.confirmation', 'mapped' => false, 'attr' => [ 'maxlength' => '6', @@ -60,7 +61,7 @@ class TFAGoogleSettingsType extends AbstractType 'pattern' => '\d*', 'autocomplete' => 'off', ], - 'constraints' => [new ValidGoogleAuthCode()], + 'constraints' => [new ValidGoogleAuthCode(groups: ["google_authenticator"])], ] ); @@ -92,6 +93,7 @@ class TFAGoogleSettingsType extends AbstractType { $resolver->setDefaults([ 'data_class' => User::class, + 'validation_groups' => ['google_authenticator'], ]); } } diff --git a/src/Form/Type/APIKeyType.php b/src/Form/Type/APIKeyType.php new file mode 100644 index 00000000..57eaea96 --- /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 07f0a9f8..875ca35f 100644 --- a/src/Form/Type/CurrencyEntityType.php +++ b/src/Form/Type/CurrencyEntityType.php @@ -25,6 +25,7 @@ namespace App\Form\Type; 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 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ 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 @@ class CurrencyEntityType extends StructuralEntityType $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/ExponentialNumberType.php b/src/Form/Type/ExponentialNumberType.php new file mode 100644 index 00000000..f566afbb --- /dev/null +++ b/src/Form/Type/ExponentialNumberType.php @@ -0,0 +1,61 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Type; + +use App\Form\Type\Helper\ExponentialNumberTransformer; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Similar to the NumberType, but formats small values in scienfitic notation instead of rounding it to 0, like NumberType + */ +class ExponentialNumberType extends AbstractType +{ + public function getParent(): string + { + return NumberType::class; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + //We want to allow the full precision of the number, so disable rounding + 'scale' => null, + ]); + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->resetViewTransformers(); + + $builder->addViewTransformer(new ExponentialNumberTransformer( + $options['scale'], + $options['grouping'], + $options['rounding_mode'], + $options['html5'] ? 'en' : null + )); + } +} \ No newline at end of file diff --git a/src/Form/Type/Helper/ExponentialNumberTransformer.php b/src/Form/Type/Helper/ExponentialNumberTransformer.php new file mode 100644 index 00000000..ee2f4a4c --- /dev/null +++ b/src/Form/Type/Helper/ExponentialNumberTransformer.php @@ -0,0 +1,113 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Type\Helper; + +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Extension\Core\DataTransformer\NumberToLocalizedStringTransformer; + +/** + * This transformer formats small values in scienfitic notation instead of rounding it to 0, like the default + * NumberFormatter. + */ +class ExponentialNumberTransformer extends NumberToLocalizedStringTransformer +{ + public function __construct( + private ?int $scale = null, + ?bool $grouping = false, + ?int $roundingMode = \NumberFormatter::ROUND_HALFUP, + protected ?string $locale = null + ) { + //Set scale to null, to disable rounding of values + parent::__construct($scale, $grouping, $roundingMode, $locale); + } + + /** + * Transforms a number type into localized number. + * + * @param int|float|null $value Number value + * + * @throws TransformationFailedException if the given value is not numeric + * or if the value cannot be transformed + */ + public function transform(mixed $value): string + { + if (null === $value) { + return ''; + } + + if (!is_numeric($value)) { + throw new TransformationFailedException('Expected a numeric.'); + } + + //If the value is too small, the number formatter would return 0, therfore use exponential notation for small numbers + if (abs($value) < 1e-3) { + $formatter = $this->getScientificNumberFormatter(); + } else { + $formatter = $this->getNumberFormatter(); + } + + + + $value = $formatter->format($value); + + if (intl_is_failure($formatter->getErrorCode())) { + throw new TransformationFailedException($formatter->getErrorMessage()); + } + + // Convert non-breaking and narrow non-breaking spaces to normal ones + $value = str_replace(["\xc2\xa0", "\xe2\x80\xaf"], ' ', $value); + + return $value; + } + + protected function getScientificNumberFormatter(): \NumberFormatter + { + $formatter = new \NumberFormatter($this->locale ?? \Locale::getDefault(), \NumberFormatter::SCIENTIFIC); + + if (null !== $this->scale) { + $formatter->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, $this->scale); + $formatter->setAttribute(\NumberFormatter::ROUNDING_MODE, $this->roundingMode); + } + + $formatter->setAttribute(\NumberFormatter::GROUPING_USED, (int) $this->grouping); + + return $formatter; + } + + protected function getNumberFormatter(): \NumberFormatter + { + $formatter = parent::getNumberFormatter(); + + //Unset the fraction digits, as we don't want to round the number + $formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, 0); + if (null !== $this->scale) { + $formatter->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, $this->scale); + } else { + $formatter->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, 100); + } + + + return $formatter; + } +} \ No newline at end of file diff --git a/src/Form/Type/Helper/StructuralEntityChoiceHelper.php b/src/Form/Type/Helper/StructuralEntityChoiceHelper.php index 402270ce..1210d188 100644 --- a/src/Form/Type/Helper/StructuralEntityChoiceHelper.php +++ b/src/Form/Type/Helper/StructuralEntityChoiceHelper.php @@ -43,7 +43,7 @@ class StructuralEntityChoiceHelper /** * Generates the choice attributes for the given AbstractStructuralDBElement. - * @return array|string[] + * @return array */ public function generateChoiceAttr(AbstractNamedDBElement $choice, Options|array $options): array { @@ -75,7 +75,8 @@ class StructuralEntityChoiceHelper } if ($choice instanceof HasMasterAttachmentInterface) { - $tmp['data-image'] = $choice->getMasterPictureAttachment() instanceof Attachment ? + $tmp['data-image'] = ($choice->getMasterPictureAttachment() instanceof Attachment + && $choice->getMasterPictureAttachment()->isPicture()) ? $this->attachmentURLGenerator->getThumbnailURL($choice->getMasterPictureAttachment(), 'thumbnail_xs') : null @@ -99,7 +100,7 @@ class StructuralEntityChoiceHelper public function generateChoiceAttrCurrency(Currency $choice, Options|array $options): array { $tmp = $this->generateChoiceAttr($choice, $options); - $symbol = empty($choice->getIsoCode()) ? null : Currencies::getSymbol($choice->getIsoCode()); + $symbol = $choice->getIsoCode() === '' ? null : Currencies::getSymbol($choice->getIsoCode()); $tmp['data-short'] = $options['short'] ? $symbol : $choice->getName(); //Show entities that are not added to DB yet separately from other entities @@ -136,9 +137,10 @@ class StructuralEntityChoiceHelper if ($element->getID() === null) { if ($element instanceof AbstractStructuralDBElement) { //Must be the same as the separator in the choice_loader, otherwise this will not work! - return $element->getFullPath('->'); + return '$%$' . $element->getFullPath('->'); } - return $element->getName(); + // '$%$' is the indicator prefix for a new entity + return '$%$' . $element->getName(); } return $element->getID(); diff --git a/src/Form/Type/Helper/StructuralEntityChoiceLoader.php b/src/Form/Type/Helper/StructuralEntityChoiceLoader.php index 87658398..e2e4e841 100644 --- a/src/Form/Type/Helper/StructuralEntityChoiceLoader.php +++ b/src/Form/Type/Helper/StructuralEntityChoiceLoader.php @@ -20,6 +20,7 @@ declare(strict_types=1); * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + namespace App\Form\Type\Helper; use App\Entity\Base\AbstractNamedDBElement; @@ -28,26 +29,34 @@ use App\Repository\StructuralDBElementRepository; use App\Services\Trees\NodesListBuilder; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\ChoiceList\Loader\AbstractChoiceLoader; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormInterface; use Symfony\Component\OptionsResolver\Options; +use Symfony\Contracts\Translation\TranslatorInterface; +/** + * @template T of AbstractStructuralDBElement + */ class StructuralEntityChoiceLoader extends AbstractChoiceLoader { private ?string $additional_element = null; private ?AbstractNamedDBElement $starting_element = null; - public function __construct(private readonly Options $options, private readonly NodesListBuilder $builder, private readonly EntityManagerInterface $entityManager) - { + private ?FormInterface $form = null; + + public function __construct( + private readonly Options $options, + private readonly NodesListBuilder $builder, + private readonly EntityManagerInterface $entityManager, + private readonly TranslatorInterface $translator + ) { } protected function loadChoices(): iterable { //If the starting_element is set and not persisted yet, add it to the list - if ($this->starting_element !== null && $this->starting_element->getID() === null) { - $tmp = [$this->starting_element]; - } else { - $tmp = []; - } + $tmp = $this->starting_element !== null && $this->starting_element->getID() === null ? [$this->starting_element] : []; if ($this->additional_element) { $tmp = $this->createNewEntitiesFromValue($this->additional_element); @@ -67,28 +76,43 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader if ($this->starting_element !== null && $this->starting_element->getID() === null //Element must not be persisted yet && $this->options['choice_value']($this->starting_element) === $value) { - //Then reuse the starting element $this->entityManager->persist($this->starting_element); return [$this->starting_element]; } + if (!$this->options['allow_add']) { - throw new \RuntimeException('Cannot create new entity, because allow_add is not enabled!'); + //If we have a form, add an error to it, to improve the user experience + if ($this->form !== null) { + $this->form->addError( + new FormError($this->translator->trans('entity.select.creating_new_entities_not_allowed') + ) + ); + } else { + throw new \RuntimeException('Cannot create new entity, because allow_add is not enabled!'); + } } + + /** @var class-string $class */ $class = $this->options['class']; - /** @var StructuralDBElementRepository $repo */ + + /** @var StructuralDBElementRepository $repo */ $repo = $this->entityManager->getRepository($class); + $entities = $repo->getNewEntityFromPath($value, '->'); $results = []; - foreach($entities as $entity) { + foreach ($entities as $entity) { //If the entity is newly created (ID null), add it as result and persist it. if ($entity->getID() === null) { - $this->entityManager->persist($entity); + //Only persist the entities if it is allowed + if ($this->options['allow_add']) { + $this->entityManager->persist($entity); + } $results[] = $entity; } } @@ -115,6 +139,16 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader return $this->starting_element; } + /** + * Sets the form that this loader is bound to. + * @param FormInterface|null $form + * @return void + */ + public function setForm(?FormInterface $form): void + { + $this->form = $form; + } + /** * Sets the initial value used to populate the field. This will always be an allowed value. * @param AbstractNamedDBElement|null $starting_element @@ -126,5 +160,20 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader return $this; } + protected function doLoadChoicesForValues(array $values, ?callable $value): array + { + // Normalize the data (remove whitespaces around the arrow sign) and leading/trailing whitespaces + // This is required so that the value that is generated for an new entity based on its name structure is + // the same as the value that is generated for the same entity after it is persisted. + // Otherwise, errors occurs that the element could not be found. + foreach ($values as &$data) { + $data = trim((string) $data); + $data = preg_replace('/\s*->\s*/', '->', $data); + } + unset ($data); + + return $this->loadChoiceList($value)->getChoicesForValues($values); + } + } diff --git a/src/Form/Type/LocaleSelectType.php b/src/Form/Type/LocaleSelectType.php new file mode 100644 index 00000000..6dc6f9fc --- /dev/null +++ b/src/Form/Type/LocaleSelectType.php @@ -0,0 +1,52 @@ +. + */ + +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/PartLotSelectType.php b/src/Form/Type/PartLotSelectType.php index 8eff5122..c68535a7 100644 --- a/src/Form/Type/PartLotSelectType.php +++ b/src/Form/Type/PartLotSelectType.php @@ -22,7 +22,7 @@ declare(strict_types=1); */ namespace App\Form\Type; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; use Doctrine\ORM\EntityRepository; @@ -46,7 +46,7 @@ class PartLotSelectType extends AbstractType $resolver->setDefaults([ 'class' => PartLot::class, - 'choice_label' => ChoiceList::label($this, static fn(PartLot $part_lot): string => ($part_lot->getStorageLocation() instanceof Storelocation ? $part_lot->getStorageLocation()->getFullPath() : '') + 'choice_label' => ChoiceList::label($this, static fn(PartLot $part_lot): string => ($part_lot->getStorageLocation() instanceof StorageLocation ? $part_lot->getStorageLocation()->getFullPath() : '') . ' (' . $part_lot->getName() . '): ' . $part_lot->getAmount()), 'query_builder' => fn(Options $options) => static fn(EntityRepository $er) => $er->createQueryBuilder('l') ->where('l.part = :part') diff --git a/src/Form/Type/SIUnitType.php b/src/Form/Type/SIUnitType.php index 2f0138f0..52bcaa3d 100644 --- a/src/Form/Type/SIUnitType.php +++ b/src/Form/Type/SIUnitType.php @@ -153,7 +153,7 @@ final class SIUnitType extends AbstractType implements DataMapperInterface * * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported */ - public function mapDataToForms($viewData, \Traversable $forms): void + public function mapDataToForms(mixed $viewData, \Traversable $forms): void { $forms = iterator_to_array($forms); @@ -204,7 +204,7 @@ final class SIUnitType extends AbstractType implements DataMapperInterface * * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported */ - public function mapFormsToData(\Traversable $forms, &$viewData): void + public function mapFormsToData(\Traversable $forms, mixed &$viewData): void { //Convert both fields to a single float value. diff --git a/src/Form/Type/StaticFileAutocompleteType.php b/src/Form/Type/StaticFileAutocompleteType.php new file mode 100644 index 00000000..4d483e2a --- /dev/null +++ b/src/Form/Type/StaticFileAutocompleteType.php @@ -0,0 +1,63 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Type; + +use Symfony\Component\Asset\Packages; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Implements a text type with autocomplete functionality based on a static file, containing a list of autocomplete + * suggestions. + * Other values are allowed, but the user can select from the list of suggestions. + * The file must be located in the public directory! + */ +class StaticFileAutocompleteType extends AbstractType +{ + public function __construct( + private readonly Packages $assets + ) { + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setRequired('file'); + $resolver->setAllowedTypes('file', 'string'); + } + + public function getParent(): string + { + return TextType::class; + } + + public function finishView(FormView $view, FormInterface $form, array $options): void + { + //Add the data-controller and data-url attributes to the form field + $view->vars['attr']['data-controller'] = 'elements--static-file-autocomplete'; + $view->vars['attr']['data-url'] = $this->assets->getUrl($options['file']); + } +} \ No newline at end of file diff --git a/src/Form/Type/StructuralEntityType.php b/src/Form/Type/StructuralEntityType.php index 18368289..51eb21a1 100644 --- a/src/Form/Type/StructuralEntityType.php +++ b/src/Form/Type/StructuralEntityType.php @@ -51,11 +51,14 @@ class StructuralEntityType extends AbstractType public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->addEventListener(FormEvents::PRE_SUBMIT, function (PreSubmitEvent $event) { - //When the data contains non-digit characters, we assume that the user entered a new element. + //When the data starts with "$%$", we assume that the user entered a new element. //In that case we add the new element to our choice_loader $data = $event->getData(); - if (null === $data || !is_string($data) || $data === "" || ctype_digit($data)) { + if (is_string($data) && str_starts_with($data, '$%$')) { + //Extract the real name from the data + $data = substr($data, 3); + } else { return; } @@ -64,6 +67,7 @@ class StructuralEntityType extends AbstractType $choice_loader = $options['choice_loader']; if ($choice_loader instanceof StructuralEntityChoiceLoader) { $choice_loader->setAdditionalElement($data); + $choice_loader->setForm($form); } }); @@ -80,7 +84,7 @@ class StructuralEntityType extends AbstractType 'subentities_of' => null, //Only show entities with the given parent class 'disable_not_selectable' => false, //Disable entries with not selectable property 'choice_value' => fn(?AbstractNamedDBElement $element) => $this->choice_helper->generateChoiceValue($element), //Use the element id as option value and for comparing items - 'choice_loader' => fn(Options $options) => new StructuralEntityChoiceLoader($options, $this->builder, $this->em), + 'choice_loader' => fn(Options $options) => new StructuralEntityChoiceLoader($options, $this->builder, $this->em, $this->translator), 'choice_label' => fn(Options $options) => fn($choice, $key, $value) => $this->choice_helper->generateChoiceLabel($choice), 'choice_attr' => fn(Options $options) => fn($choice, $key, $value) => $this->choice_helper->generateChoiceAttr($choice, $options), 'group_by' => fn(AbstractNamedDBElement $element) => $this->choice_helper->generateGroupBy($element), @@ -104,14 +108,12 @@ class StructuralEntityType extends AbstractType $resolver->setDefault('dto_value', null); $resolver->setAllowedTypes('dto_value', ['null', 'string']); //If no help text is explicitly set, we use the dto value as help text and show it as html - $resolver->setDefault('help', function (Options $options) { - return $this->dtoText($options['dto_value']); - }); - $resolver->setDefault('help_html', function (Options $options) { - return $options['dto_value'] !== null; - }); + $resolver->setDefault('help', fn(Options $options) => $this->dtoText($options['dto_value'])); + $resolver->setDefault('help_html', fn(Options $options) => $options['dto_value'] !== null); + - $resolver->setDefault('attr', function (Options $options) { + //Normalize the attr to merge custom attributes + $resolver->setNormalizer('attr', function (Options $options, $value) { $tmp = [ 'data-controller' => $options['controller'], 'data-allow-add' => $options['allow_add'] ? 'true' : 'false', @@ -121,7 +123,7 @@ class StructuralEntityType extends AbstractType $tmp['data-empty-message'] = $options['empty_message']; } - return $tmp; + return array_merge($tmp, $value); }); } diff --git a/src/Form/Type/TriStateCheckboxType.php b/src/Form/Type/TriStateCheckboxType.php index df4b0f3a..b2a85ad3 100644 --- a/src/Form/Type/TriStateCheckboxType.php +++ b/src/Form/Type/TriStateCheckboxType.php @@ -99,9 +99,8 @@ final class TriStateCheckboxType extends AbstractType implements DataTransformer * * @return mixed The value in the transformed representation * - * @throws TransformationFailedException when the transformation fails */ - public function transform($value) + public function transform(mixed $value): mixed { if (true === $value) { return 'true'; @@ -142,10 +141,8 @@ final class TriStateCheckboxType extends AbstractType implements DataTransformer * @param mixed $value The value in the transformed representation * * @return mixed The value in the original representation - * - * @throws TransformationFailedException when the transformation fails */ - public function reverseTransform($value) + public function reverseTransform(mixed $value): mixed { return match ($value) { 'true' => true, diff --git a/src/Form/Type/UserSelectType.php b/src/Form/Type/UserSelectType.php index cc16d724..8862cdf7 100644 --- a/src/Form/Type/UserSelectType.php +++ b/src/Form/Type/UserSelectType.php @@ -34,7 +34,7 @@ class UserSelectType extends AbstractType { $resolver->setDefaults([ 'class' => User::class, - 'choice_label' => fn(Options $options) => fn(User $choice, $key, $value) => $choice->getFullName(true), + 'choice_label' => fn(Options $options) => static fn(User $choice, $key, $value) => $choice->getFullName(true), ]); } diff --git a/src/Form/UserAdminForm.php b/src/Form/UserAdminForm.php index d1e5924e..69be181f 100644 --- a/src/Form/UserAdminForm.php +++ b/src/Form/UserAdminForm.php @@ -22,6 +22,7 @@ declare(strict_types=1); 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 App\Form\Type\ThemeChoiceType; 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; @@ -57,6 +57,8 @@ class UserAdminForm extends AbstractType parent::configureOptions($resolver); // TODO: Change the autogenerated stub $resolver->setRequired('attachment_class'); $resolver->setDefault('parameter_class', false); + + $resolver->setDefault('validation_groups', ['Default', 'permissions:edit']); } public function buildForm(FormBuilderInterface $builder, array $options): void @@ -138,11 +140,10 @@ class UserAdminForm extends AbstractType ]) //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 53ca8cf8..0c7cb169 100644 --- a/src/Form/UserSettingsType.php +++ b/src/Form/UserSettingsType.php @@ -22,17 +22,18 @@ declare(strict_types=1); namespace App\Form; +use App\Form\Type\LocaleSelectType; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\UserSystem\User; use App\Form\Type\CurrencyEntityType; use App\Form\Type\RichTextEditorType; use App\Form\Type\ThemeChoiceType; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Event\PreSetDataEvent; 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; @@ -44,7 +45,9 @@ use Symfony\Component\Validator\Constraints\File; class UserSettingsType extends AbstractType { - public function __construct(protected Security $security, protected bool $demo_mode) + public function __construct(protected Security $security, + protected bool $demo_mode, + ) { } @@ -90,7 +93,7 @@ class UserSettingsType extends AbstractType ], 'constraints' => [ new File([ - 'maxSize' => '2M', + 'maxSize' => '5M', ]), ], ]) @@ -104,12 +107,11 @@ class UserSettingsType extends AbstractType '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' => ['en', 'de'], ]) ->add('timezone', TimezoneType::class, [ 'disabled' => $this->demo_mode, diff --git a/src/Helpers/FilenameSanatizer.php b/src/Helpers/FilenameSanatizer.php new file mode 100644 index 00000000..f6744b1a --- /dev/null +++ b/src/Helpers/FilenameSanatizer.php @@ -0,0 +1,58 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Helpers; + +/** + * This class provides functions to sanitize filenames. + */ +class FilenameSanatizer +{ + /** + * Converts a given filename to a version, which is guaranteed to be safe to use on all filesystems. + * This function is adapted from https://stackoverflow.com/a/42058764/21879970 + * @param string $filename + * @return string + */ + public static function sanitizeFilename(string $filename): string + { + //Convert to ASCII + $filename = iconv('UTF-8', 'ASCII//TRANSLIT', $filename); + + $filename = preg_replace( + '~ + [<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words + [\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx + [\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN + [#\[\]@!$&\'()+,;=]| # URI reserved https://www.rfc-editor.org/rfc/rfc3986#section-2.2 + [{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt + ~x', + '-', $filename); + + // avoids ".", ".." or ".hiddenFiles" + $filename = ltrim((string) $filename, '.-'); + //Limit filename length to 255 bytes + $ext = pathinfo($filename, PATHINFO_EXTENSION); + return mb_strcut(pathinfo($filename, PATHINFO_FILENAME), 0, 255 - ($ext !== '' && $ext !== '0' ? strlen($ext) + 1 : 0), mb_detect_encoding($filename)) . ($ext !== '' && $ext !== '0' ? '.' . $ext : ''); + } +} \ No newline at end of file diff --git a/src/Helpers/IPAnonymizer.php b/src/Helpers/IPAnonymizer.php new file mode 100644 index 00000000..9662852f --- /dev/null +++ b/src/Helpers/IPAnonymizer.php @@ -0,0 +1,49 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Helpers; + +use Symfony\Component\HttpFoundation\IpUtils; + +/** + * Utils to assist with IP anonymization. + * The IPUtils::anonymize has a certain edgecase with local-link addresses, which is handled here. + * See: https://github.com/Part-DB/Part-DB-server/issues/782 + */ +final class IPAnonymizer +{ + public static function anonymize(string $ip): string + { + /** + * If the IP contains a % symbol, then it is a local-link address with scoping according to RFC 4007 + * In that case, we only care about the part before the % symbol, as the following functions, can only work with + * the IP address itself. As the scope can leak information (containing interface name), we do not want to + * include it in our anonymized IP data. + */ + if (str_contains($ip, '%')) { + $ip = substr($ip, 0, strpos($ip, '%')); + } + + return IpUtils::anonymize($ip); + } +} \ No newline at end of file diff --git a/src/Helpers/LabelResponse.php b/src/Helpers/LabelResponse.php index 98451e2f..2973eb7e 100644 --- a/src/Helpers/LabelResponse.php +++ b/src/Helpers/LabelResponse.php @@ -84,7 +84,7 @@ class LabelResponse extends Response */ public function setAutoLastModified(): LabelResponse { - $this->setLastModified(new DateTime()); + $this->setLastModified(new \DateTimeImmutable()); return $this; } diff --git a/src/Helpers/Projects/ProjectBuildRequest.php b/src/Helpers/Projects/ProjectBuildRequest.php index c2c2ad90..430d37b5 100644 --- a/src/Helpers/Projects/ProjectBuildRequest.php +++ b/src/Helpers/Projects/ProjectBuildRequest.php @@ -174,11 +174,7 @@ final class ProjectBuildRequest */ public function getLotWithdrawAmount(PartLot|int $lot): float { - if ($lot instanceof PartLot) { - $lot_id = $lot->getID(); - } else { // Then it must be an int - $lot_id = $lot; - } + $lot_id = $lot instanceof PartLot ? $lot->getID() : $lot; if (! array_key_exists($lot_id, $this->withdraw_amounts)) { throw new \InvalidArgumentException('The given lot is not in the withdraw amounts array!'); diff --git a/src/Helpers/TrinaryLogicHelper.php b/src/Helpers/TrinaryLogicHelper.php index 54ab9bf8..f4b460de 100644 --- a/src/Helpers/TrinaryLogicHelper.php +++ b/src/Helpers/TrinaryLogicHelper.php @@ -26,6 +26,7 @@ namespace App\Helpers; /** * Helper functions for logic operations with trinary logic. * True and false are represented as classical boolean values, undefined is represented as null. + * @see \App\Tests\Helpers\TrinaryLogicHelperTest */ class TrinaryLogicHelper { diff --git a/src/Migration/AbstractMultiPlatformMigration.php b/src/Migration/AbstractMultiPlatformMigration.php index 54e3b529..bc2b3f19 100644 --- a/src/Migration/AbstractMultiPlatformMigration.php +++ b/src/Migration/AbstractMultiPlatformMigration.php @@ -25,7 +25,8 @@ namespace App\Migration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; use Psr\Log\LoggerInterface; @@ -35,6 +36,9 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration final public const ADMIN_PW_LENGTH = 10; protected string $admin_pw = ''; + /** @noinspection SenselessProxyMethodInspection + * This method is required to redefine the logger type hint to protected + */ public function __construct(Connection $connection, protected LoggerInterface $logger) { parent::__construct($connection, $logger); @@ -47,6 +51,7 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration match ($db_type) { 'mysql' => $this->mySQLUp($schema), 'sqlite' => $this->sqLiteUp($schema), + 'postgresql' => $this->postgreSQLUp($schema), default => $this->abortIf(true, "Database type '$db_type' is not supported!"), }; } @@ -58,6 +63,7 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration match ($db_type) { 'mysql' => $this->mySQLDown($schema), 'sqlite' => $this->sqLiteDown($schema), + 'postgresql' => $this->postgreSQLDown($schema), default => $this->abortIf(true, "Database type is not supported!"), }; } @@ -132,6 +138,24 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration return $result > 0; } + /** + * Checks if a column exists in a table. + * @return bool Returns true, if the column exists + * @throws Exception + */ + public function doesColumnExist(string $table, string $column_name): bool + { + $db_type = $this->getDatabaseType(); + if ($db_type !== 'mysql') { + throw new \RuntimeException('This method is only supported for MySQL/MariaDB databases!'); + } + + $sql = "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '$table' AND COLUMN_NAME = '$column_name'"; + $result = (int) $this->connection->fetchOne($sql); + + return $result > 0; + } + /** * Returns the database type of the used database. * @return string|null Returns 'mysql' for MySQL/MariaDB and 'sqlite' for SQLite. Returns null if unknown type @@ -142,10 +166,14 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration return 'mysql'; } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { return 'sqlite'; } + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + return 'postgresql'; + } + return null; } @@ -156,4 +184,8 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration abstract public function sqLiteUp(Schema $schema): void; abstract public function sqLiteDown(Schema $schema): void; + + abstract public function postgreSQLUp(Schema $schema): void; + + abstract public function postgreSQLDown(Schema $schema): void; } diff --git a/src/Migration/WithPermPresetsTrait.php b/src/Migration/WithPermPresetsTrait.php new file mode 100644 index 00000000..203ef68a --- /dev/null +++ b/src/Migration/WithPermPresetsTrait.php @@ -0,0 +1,72 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Migration; + +use App\Entity\UserSystem\PermissionData; +use App\Security\Interfaces\HasPermissionsInterface; +use App\Services\UserSystem\PermissionPresetsHelper; +use Psr\Container\ContainerInterface; + +trait WithPermPresetsTrait +{ + private ?ContainerInterface $container = null; + private ?PermissionPresetsHelper $permission_presets_helper = null; + + private function getJSONPermDataFromPreset(string $preset): string + { + if ($this->permission_presets_helper === null) { + throw new \RuntimeException('PermissionPresetsHelper not set! There seems to be some issue with the dependency injection!'); + } + + //Create a virtual user on which we can apply the preset + $user = new class implements HasPermissionsInterface { + + public PermissionData $perm_data; + + public function __construct() + { + $this->perm_data = new PermissionData(); + } + + public function getPermissions(): PermissionData + { + return $this->perm_data; + } + }; + + //Apply the preset to the virtual user + $this->permission_presets_helper->applyPreset($user, $preset); + + //And return the json data + return json_encode($user->getPermissions()); + } + + public function setContainer(?ContainerInterface $container = null): void + { + if ($container !== null) { + $this->container = $container; + $this->permission_presets_helper = $container->get(PermissionPresetsHelper::class); + } + } +} \ No newline at end of file diff --git a/src/Repository/AbstractPartsContainingRepository.php b/src/Repository/AbstractPartsContainingRepository.php index 3a389610..4fd0bbff 100644 --- a/src/Repository/AbstractPartsContainingRepository.php +++ b/src/Repository/AbstractPartsContainingRepository.php @@ -40,11 +40,11 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo * Returns all parts associated with this element. * * @param object $element the element for which the parts should be determined - * @param array $order_by The order of the parts. Format ['name' => 'ASC'] + * @param string $nameOrderDirection the direction in which the parts should be ordered by name, either ASC or DESC * * @return Part[] */ - abstract public function getParts(object $element, array $order_by = ['name' => 'ASC']): array; + abstract public function getParts(object $element, string $nameOrderDirection = "ASC"): array; /** * Gets the count of the parts associated with this element. @@ -64,6 +64,11 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo return $this->getPartsCountRecursiveWithDepthN($element, self::RECURSION_LIMIT); } + public function getPartsRecursive(AbstractPartsContainingDBElement $element): array + { + return $this->getPartsRecursiveWithDepthN($element, self::RECURSION_LIMIT); + } + /** * The implementation of the recursive function to get the parts count. * This function is used to limit the recursion depth (remaining_depth is decreased on each call). @@ -91,7 +96,24 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo return $count; } - protected function getPartsByField(object $element, array $order_by, string $field_name): array + protected function getPartsRecursiveWithDepthN(AbstractPartsContainingDBElement $element, int $remaining_depth): array + { + if ($remaining_depth <= 0) { + throw new \RuntimeException('Recursion limit reached!'); + } + + //Add direct parts + $parts = $this->getParts($element); + + //Then iterate over all children and add their parts + foreach ($element->getChildren() as $child) { + $parts = array_merge($parts, $this->getPartsRecursiveWithDepthN($child, $remaining_depth - 1)); + } + + return $parts; + } + + protected function getPartsByField(object $element, string $nameOrderDirection, string $field_name): array { if (!$element instanceof AbstractPartsContainingDBElement) { throw new InvalidArgumentException('$element must be an instance of AbstractPartContainingDBElement!'); @@ -99,7 +121,14 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo $repo = $this->getEntityManager()->getRepository(Part::class); - return $repo->findBy([$field_name => $element], $order_by); + //Build a query builder to get the parts with a custom order by + + $qb = $repo->createQueryBuilder('part') + ->where('part.'.$field_name.' = :element') + ->setParameter('element', $element) + ->orderBy('NATSORT(part.name)', $nameOrderDirection); + + return $qb->getQuery()->getResult(); } protected function getPartsCountByField(object $element, string $field_name): int diff --git a/src/Repository/AttachmentContainingDBElementRepository.php b/src/Repository/AttachmentContainingDBElementRepository.php index 7f00f87f..40869662 100644 --- a/src/Repository/AttachmentContainingDBElementRepository.php +++ b/src/Repository/AttachmentContainingDBElementRepository.php @@ -25,11 +25,12 @@ namespace App\Repository; use App\Doctrine\Helpers\FieldHelper; use App\Entity\Attachments\AttachmentContainingDBElement; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; /** * @template TEntityClass of AttachmentContainingDBElement * @extends NamedDBElementRepository + * @see \App\Tests\Repository\AttachmentContainingDBElementRepositoryTest */ class AttachmentContainingDBElementRepository extends NamedDBElementRepository { @@ -70,7 +71,7 @@ class AttachmentContainingDBElementRepository extends NamedDBElementRepository $q = $qb->getQuery(); - $q->setFetchMode($this->getEntityName(), 'master_picture_attachment', ClassMetadataInfo::FETCH_EAGER); + $q->setFetchMode($this->getEntityName(), 'master_picture_attachment', ClassMetadata::FETCH_EAGER); $result = $q->getResult(); diff --git a/src/Repository/AttachmentRepository.php b/src/Repository/AttachmentRepository.php index 240ab058..4fc0abc9 100644 --- a/src/Repository/AttachmentRepository.php +++ b/src/Repository/AttachmentRepository.php @@ -58,15 +58,15 @@ class AttachmentRepository extends DBElementRepository { $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 @@ class AttachmentRepository extends DBElementRepository { $qb = $this->createQueryBuilder('attachment'); $qb->select('COUNT(attachment)') - ->where('attachment.path LIKE :http') - ->orWhere('attachment.path LIKE :https'); - $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 @@ class AttachmentRepository extends DBElementRepository { $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/CurrencyRepository.php b/src/Repository/CurrencyRepository.php index 47642f4b..63473229 100644 --- a/src/Repository/CurrencyRepository.php +++ b/src/Repository/CurrencyRepository.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace App\Repository; -use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\PriceInformations\Currency; use Symfony\Component\Intl\Currencies; diff --git a/src/Repository/DBElementRepository.php b/src/Repository/DBElementRepository.php index 296f2a9a..2437e848 100644 --- a/src/Repository/DBElementRepository.php +++ b/src/Repository/DBElementRepository.php @@ -43,13 +43,13 @@ namespace App\Repository; use App\Doctrine\Helpers\FieldHelper; use App\Entity\Base\AbstractDBElement; -use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\ORM\EntityRepository; use ReflectionClass; /** * @template TEntityClass of AbstractDBElement * @extends EntityRepository + * @see \App\Tests\Repository\DBElementRepositoryTest */ class DBElementRepository extends EntityRepository { @@ -80,7 +80,7 @@ class DBElementRepository extends EntityRepository /** * Find all elements that match a list of IDs. - * + * They are ordered by IDs in an ascending order. * @return AbstractDBElement[] * @phpstan-return list */ @@ -90,6 +90,7 @@ class DBElementRepository extends EntityRepository $q = $qb->select('element') ->where('element.id IN (?1)') ->setParameter(1, $ids) + ->orderBy('element.id', 'ASC') ->getQuery(); return $q->getResult(); @@ -143,9 +144,7 @@ class DBElementRepository extends EntityRepository */ protected function sortResultArrayByIDArray(array &$result_array, array $ids): void { - usort($result_array, static function (AbstractDBElement $a, AbstractDBElement $b) use ($ids) { - return array_search($a->getID(), $ids, true) <=> array_search($b->getID(), $ids, true); - }); + usort($result_array, static fn(AbstractDBElement $a, AbstractDBElement $b) => array_search($a->getID(), $ids, true) <=> array_search($b->getID(), $ids, true)); } protected function setField(AbstractDBElement $element, string $field, int $new_value): void diff --git a/src/Repository/LabelProfileRepository.php b/src/Repository/LabelProfileRepository.php index 9fa5d3cc..ad9e40f1 100644 --- a/src/Repository/LabelProfileRepository.php +++ b/src/Repository/LabelProfileRepository.php @@ -45,7 +45,6 @@ use App\Entity\LabelSystem\LabelOptions; use App\Entity\LabelSystem\LabelProfile; use App\Entity\LabelSystem\LabelSupportedElement; use App\Helpers\Trees\TreeViewNode; -use InvalidArgumentException; /** * @template TEntityClass of LabelProfile diff --git a/src/Repository/LogEntryRepository.php b/src/Repository/LogEntryRepository.php index 472993a7..6850d06b 100644 --- a/src/Repository/LogEntryRepository.php +++ b/src/Repository/LogEntryRepository.php @@ -30,7 +30,6 @@ use App\Entity\LogSystem\ElementDeletedLogEntry; use App\Entity\LogSystem\ElementEditedLogEntry; use App\Entity\LogSystem\LogTargetType; use App\Entity\UserSystem\User; -use DateTime; use RuntimeException; /** @@ -86,10 +85,8 @@ class LogEntryRepository extends DBElementRepository ->orderBy('log.timestamp', 'DESC') ->setMaxResults(1); - $qb->setParameters([ - 'target_type' => LogTargetType::fromElementClass($class), - 'target_id' => $id, - ]); + $qb->setParameter('target_type', LogTargetType::fromElementClass($class)); + $qb->setParameter('target_id', $id); $query = $qb->getQuery(); @@ -114,19 +111,18 @@ class LogEntryRepository extends DBElementRepository { $qb = $this->createQueryBuilder('log'); $qb->select('log') - //->where('log INSTANCE OF App\Entity\LogSystem\ElementEditedLogEntry') ->where('log INSTANCE OF '.ElementEditedLogEntry::class) ->orWhere('log INSTANCE OF '.CollectionElementDeleted::class) ->andWhere('log.target_type = :target_type') ->andWhere('log.target_id = :target_id') ->andWhere('log.timestamp >= :until') - ->orderBy('log.timestamp', 'DESC'); + ->orderBy('log.timestamp', 'DESC') + ; + + $qb->setParameter('target_type', LogTargetType::fromElementClass($element)); + $qb->setParameter('target_id', $element->getID()); + $qb->setParameter('until', $until); - $qb->setParameters([ - 'target_type' => LogTargetType::fromElementClass($element), - 'target_id' => $element->getID(), - 'until' => $until, - ]); $query = $qb->getQuery(); @@ -146,13 +142,11 @@ class LogEntryRepository extends DBElementRepository ->andWhere('log.target_type = :target_type') ->andWhere('log.target_id = :target_id') ->andWhere('log.timestamp >= :until') - ->orderBy('log.timestamp', 'DESC'); + ; - $qb->setParameters([ - 'target_type' => LogTargetType::fromElementClass($element), - 'target_id' => $element->getID(), - 'until' => $timestamp, - ]); + $qb->setParameter('target_type', LogTargetType::fromElementClass($element)); + $qb->setParameter('target_id', $element->getID()); + $qb->setParameter('until', $timestamp); $query = $qb->getQuery(); $count = $query->getSingleScalarResult(); @@ -163,10 +157,10 @@ class LogEntryRepository extends DBElementRepository /** * Gets the last log entries ordered by timestamp. * - * @param int|null $limit - * @param int|null $offset + * @param int|null $limit + * @param int|null $offset */ - public function getLogsOrderedByTimestamp(string $order = 'DESC', $limit = null, $offset = null): array + public function getLogsOrderedByTimestamp(string $order = 'DESC', ?int $limit = null, ?int $offset = null): array { return $this->findBy([], ['timestamp' => $order], $limit, $offset); } @@ -219,20 +213,29 @@ class LogEntryRepository extends DBElementRepository protected function getLastUser(AbstractDBElement $element, string $log_class): ?User { $qb = $this->createQueryBuilder('log'); + /** + * The select and join with user here are important, to get true null user values, if the user was deleted. + * This happens for sqlite database, before the SET NULL constraint was added, and doctrine generates a proxy + * entity which fails to resolve, without this line. + * This was the cause of issue #414 (https://github.com/Part-DB/Part-DB-server/issues/414) + */ $qb->select('log') - //->where('log INSTANCE OF App\Entity\LogSystem\ElementEditedLogEntry') + ->addSelect('user') ->where('log INSTANCE OF '.$log_class) + ->leftJoin('log.user', 'user') ->andWhere('log.target_type = :target_type') ->andWhere('log.target_id = :target_id') - ->orderBy('log.timestamp', 'DESC'); + ->orderBy('log.timestamp', 'DESC') + //Use id as fallback, if timestamp is the same (higher id means newer entry) + ->addOrderBy('log.id', 'DESC') + ; - $qb->setParameters([ - 'target_type' => LogTargetType::fromElementClass($element), - 'target_id' => $element->getID(), - ]); + $qb->setParameter('target_type', LogTargetType::fromElementClass($element)); + $qb->setParameter('target_id', $element->getID()); $query = $qb->getQuery(); $query->setMaxResults(1); + /** @var AbstractLogEntry[] $results */ $results = $query->execute(); if (isset($results[0])) { diff --git a/src/Repository/NamedDBElementRepository.php b/src/Repository/NamedDBElementRepository.php index ae8d4d8f..8c78cb5e 100644 --- a/src/Repository/NamedDBElementRepository.php +++ b/src/Repository/NamedDBElementRepository.php @@ -29,6 +29,7 @@ use App\Helpers\Trees\TreeViewNode; /** * @template TEntityClass of AbstractNamedDBElement * @extends DBElementRepository + * @see \App\Tests\Repository\NamedDBElementRepositoryTest */ class NamedDBElementRepository extends DBElementRepository { @@ -42,7 +43,7 @@ class NamedDBElementRepository extends DBElementRepository { $result = []; - $entities = $this->findBy([], ['name' => 'ASC']); + $entities = $this->getFlatList(); foreach ($entities as $entity) { /** @var AbstractNamedDBElement $entity */ $node = new TreeViewNode($entity->getName(), null, null); @@ -65,13 +66,17 @@ class NamedDBElementRepository extends DBElementRepository } /** - * Returns a flattened list of all nodes. + * Returns a flattened list of all nodes, sorted by name in natural order. * @return AbstractNamedDBElement[] * @phpstan-return array */ public function getFlatList(): array { - //All nodes are sorted by name - return $this->findBy([], ['name' => 'ASC']); + $qb = $this->createQueryBuilder('e'); + $q = $qb->select('e') + ->orderBy('NATSORT(e.name)', 'ASC') + ->getQuery(); + + return $q->getResult(); } } diff --git a/src/Repository/ParameterRepository.php b/src/Repository/ParameterRepository.php index a837435e..6c6c867d 100644 --- a/src/Repository/ParameterRepository.php +++ b/src/Repository/ParameterRepository.php @@ -44,7 +44,7 @@ class ParameterRepository extends DBElementRepository ->select('parameter.name') ->addSelect('parameter.symbol') ->addSelect('parameter.unit') - ->where('parameter.name LIKE :name'); + ->where('ILIKE(parameter.name, :name) = TRUE'); if ($exact) { $qb->setParameter('name', $name); } else { diff --git a/src/Repository/PartRepository.php b/src/Repository/PartRepository.php index 3ab3ed31..9d5fee5e 100644 --- a/src/Repository/PartRepository.php +++ b/src/Repository/PartRepository.php @@ -22,17 +22,35 @@ declare(strict_types=1); namespace App\Repository; +use App\Entity\Parts\Category; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; +use App\Settings\MiscSettings\IpnSuggestSettings; use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\NoResultException; use Doctrine\ORM\QueryBuilder; +use Symfony\Contracts\Translation\TranslatorInterface; +use Doctrine\ORM\EntityManagerInterface; /** * @extends NamedDBElementRepository */ class PartRepository extends NamedDBElementRepository { + private TranslatorInterface $translator; + private IpnSuggestSettings $ipnSuggestSettings; + + public function __construct( + EntityManagerInterface $em, + TranslatorInterface $translator, + IpnSuggestSettings $ipnSuggestSettings, + ) { + parent::__construct($em, $em->getClassMetadata(Part::class)); + + $this->translator = $translator; + $this->ipnSuggestSettings = $ipnSuggestSettings; + } + /** * Gets the summed up instock of all parts (only parts without a measurement unit). * @@ -81,17 +99,294 @@ class PartRepository extends NamedDBElementRepository ->leftJoin('part.category', 'category') ->leftJoin('part.footprint', 'footprint') - ->where('part.name LIKE :query') - ->orWhere('part.description LIKE :query') - ->orWhere('category.name LIKE :query') - ->orWhere('footprint.name LIKE :query') - ; + ->where('ILIKE(part.name, :query) = TRUE') + ->orWhere('ILIKE(part.description, :query) = TRUE') + ->orWhere('ILIKE(category.name, :query) = TRUE') + ->orWhere('ILIKE(footprint.name, :query) = TRUE'); $qb->setParameter('query', '%'.$query.'%'); $qb->setMaxResults($max_limits); - $qb->orderBy('part.name', 'ASC'); + $qb->orderBy('NATSORT(part.name)', 'ASC'); return $qb->getQuery()->getResult(); } + + /** + * Provides IPN (Internal Part Number) suggestions for a given part based on its category, description, + * and configured autocomplete digit length. + * + * This function generates suggestions for common prefixes and incremented prefixes based on + * the part's current category and its hierarchy. If the part is unsaved, a default "n.a." prefix is returned. + * + * @param Part $part The part for which autocomplete suggestions are generated. + * @param string $description description to assist in generating suggestions. + * @param int $suggestPartDigits The number of digits used in autocomplete increments. + * + * @return array An associative array containing the following keys: + * - 'commonPrefixes': List of common prefixes found for the part. + * - 'prefixesPartIncrement': Increments for the generated prefixes, including hierarchical prefixes. + */ + public function autoCompleteIpn(Part $part, string $description, int $suggestPartDigits): array + { + $category = $part->getCategory(); + $ipnSuggestions = ['commonPrefixes' => [], 'prefixesPartIncrement' => []]; + + //Show global prefix first if configured + if ($this->ipnSuggestSettings->globalPrefix !== null && $this->ipnSuggestSettings->globalPrefix !== '') { + $ipnSuggestions['commonPrefixes'][] = [ + 'title' => $this->ipnSuggestSettings->globalPrefix, + 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.global_prefix') + ]; + + $increment = $this->generateNextPossibleGlobalIncrement(); + $ipnSuggestions['prefixesPartIncrement'][] = [ + 'title' => $this->ipnSuggestSettings->globalPrefix . $increment, + 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.global_prefix') + ]; + } + + if (strlen($description) > 150) { + $description = substr($description, 0, 150); + } + + if ($description !== '' && $this->ipnSuggestSettings->useDuplicateDescription) { + // Check if the description is already used in another part, + + $suggestionByDescription = $this->getIpnSuggestByDescription($description); + + if ($suggestionByDescription !== null && $suggestionByDescription !== $part->getIpn() && $part->getIpn() !== null && $part->getIpn() !== '') { + $ipnSuggestions['prefixesPartIncrement'][] = [ + 'title' => $part->getIpn(), + 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.description.current-increment') + ]; + } + + if ($suggestionByDescription !== null) { + $ipnSuggestions['prefixesPartIncrement'][] = [ + 'title' => $suggestionByDescription, + 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.description.increment') + ]; + } + } + + // Validate the category and ensure it's an instance of Category + if ($category instanceof Category) { + $currentPath = $category->getPartIpnPrefix(); + $directIpnPrefixEmpty = $category->getPartIpnPrefix() === ''; + $currentPath = $currentPath === '' ? $this->ipnSuggestSettings->fallbackPrefix : $currentPath; + + $increment = $this->generateNextPossiblePartIncrement($currentPath, $part, $suggestPartDigits); + + $ipnSuggestions['commonPrefixes'][] = [ + 'title' => $currentPath . $this->ipnSuggestSettings->numberSeparator, + 'description' => $directIpnPrefixEmpty ? $this->translator->trans('part.edit.tab.advanced.ipn.prefix_empty.direct_category', ['%name%' => $category->getName()]) : $this->translator->trans('part.edit.tab.advanced.ipn.prefix.direct_category') + ]; + + $ipnSuggestions['prefixesPartIncrement'][] = [ + 'title' => $currentPath . $this->ipnSuggestSettings->numberSeparator . $increment, + 'description' => $directIpnPrefixEmpty ? $this->translator->trans('part.edit.tab.advanced.ipn.prefix_empty.direct_category', ['%name%' => $category->getName()]) : $this->translator->trans('part.edit.tab.advanced.ipn.prefix.direct_category.increment') + ]; + + // Process parent categories + $parentCategory = $category->getParent(); + + while ($parentCategory instanceof Category) { + // Prepend the parent category's prefix to the current path + $effectiveIPNPrefix = $parentCategory->getPartIpnPrefix() === '' ? $this->ipnSuggestSettings->fallbackPrefix : $parentCategory->getPartIpnPrefix(); + + $currentPath = $effectiveIPNPrefix . $this->ipnSuggestSettings->categorySeparator . $currentPath; + + $ipnSuggestions['commonPrefixes'][] = [ + 'title' => $currentPath . $this->ipnSuggestSettings->numberSeparator, + 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.hierarchical.no_increment') + ]; + + $increment = $this->generateNextPossiblePartIncrement($currentPath, $part, $suggestPartDigits); + + $ipnSuggestions['prefixesPartIncrement'][] = [ + 'title' => $currentPath . $this->ipnSuggestSettings->numberSeparator . $increment, + 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.hierarchical.increment') + ]; + + // Move to the next parent category + $parentCategory = $parentCategory->getParent(); + } + } elseif ($part->getID() === null) { + $ipnSuggestions['commonPrefixes'][] = [ + 'title' => $this->ipnSuggestSettings->fallbackPrefix, + 'description' => $this->translator->trans('part.edit.tab.advanced.ipn.prefix.not_saved') + ]; + } + + return $ipnSuggestions; + } + + /** + * Suggests the next IPN (Internal Part Number) based on the provided part description. + * + * Searches for parts with similar descriptions and retrieves their existing IPNs to calculate the next suggestion. + * Returns null if the description is empty or no suggestion can be generated. + * + * @param string $description The part description to search for. + * + * @return string|null The suggested IPN, or null if no suggestion is possible. + * + * @throws NonUniqueResultException + */ + public function getIpnSuggestByDescription(string $description): ?string + { + if ($description === '') { + return null; + } + + $qb = $this->createQueryBuilder('part'); + + $qb->select('part') + ->where('part.description LIKE :descriptionPattern') + ->setParameter('descriptionPattern', $description.'%') + ->orderBy('part.id', 'ASC'); + + $partsBySameDescription = $qb->getQuery()->getResult(); + $givenIpnsWithSameDescription = []; + + foreach ($partsBySameDescription as $part) { + if ($part->getIpn() === null || $part->getIpn() === '') { + continue; + } + + $givenIpnsWithSameDescription[] = $part->getIpn(); + } + + return $this->getNextIpnSuggestion($givenIpnsWithSameDescription); + } + + private function generateNextPossibleGlobalIncrement(): string + { + $qb = $this->createQueryBuilder('part'); + + + $qb->select('part.ipn') + ->where('REGEXP(part.ipn, :ipnPattern) = TRUE') + ->setParameter('ipnPattern', '^' . preg_quote($this->ipnSuggestSettings->globalPrefix, '/') . '\d+$') + ->orderBy('NATSORT(part.ipn)', 'DESC') + ->setMaxResults(1) + ; + + $highestIPN = $qb->getQuery()->getOneOrNullResult(); + if ($highestIPN !== null) { + //Remove the prefix and extract the increment part + $incrementPart = substr($highestIPN['ipn'], strlen($this->ipnSuggestSettings->globalPrefix)); + //Extract a number using regex + preg_match('/(\d+)$/', $incrementPart, $matches); + $incrementInt = isset($matches[1]) ? (int) $matches[1] + 1 : 0; + } else { + $incrementInt = 1; + } + + + return str_pad((string) $incrementInt, $this->ipnSuggestSettings->suggestPartDigits, '0', STR_PAD_LEFT); + } + + /** + * Generates the next possible increment for a part within a given category, while ensuring uniqueness. + * + * This method calculates the next available increment for a part's identifier (`ipn`) based on the current path + * and the number of digits specified for the autocomplete feature. It ensures that the generated identifier + * aligns with the expected length and does not conflict with already existing identifiers in the same category. + * + * @param string $currentPath The base path or prefix for the part's identifier. + * @param Part $currentPart The part entity for which the increment is being generated. + * @param int $suggestPartDigits The number of digits reserved for the increment. + * + * @return string The next possible increment as a zero-padded string. + * + * @throws NonUniqueResultException If the query returns non-unique results. + * @throws NoResultException If the query fails to return a result. + */ + private function generateNextPossiblePartIncrement(string $currentPath, Part $currentPart, int $suggestPartDigits): string + { + $qb = $this->createQueryBuilder('part'); + + $expectedLength = strlen($currentPath) + strlen($this->ipnSuggestSettings->categorySeparator) + $suggestPartDigits; // Path + '-' + $suggestPartDigits digits + + // Fetch all parts in the given category, sorted by their ID in ascending order + $qb->select('part') + ->where('part.ipn LIKE :ipnPattern') + ->andWhere('LENGTH(part.ipn) = :expectedLength') + ->setParameter('ipnPattern', $currentPath . '%') + ->setParameter('expectedLength', $expectedLength) + ->orderBy('part.id', 'ASC'); + + $parts = $qb->getQuery()->getResult(); + + // Collect all used increments in the category + $usedIncrements = []; + foreach ($parts as $part) { + if ($part->getIpn() === null || $part->getIpn() === '') { + continue; + } + + if ($part->getId() === $currentPart->getId() && $currentPart->getID() !== null) { + // Extract and return the current part's increment directly + $incrementPart = substr($part->getIpn(), -$suggestPartDigits); + if (is_numeric($incrementPart)) { + return str_pad((string) $incrementPart, $suggestPartDigits, '0', STR_PAD_LEFT); + } + } + + // Extract last $autocompletePartDigits digits for possible available part increment + $incrementPart = substr($part->getIpn(), -$suggestPartDigits); + if (is_numeric($incrementPart)) { + $usedIncrements[] = (int) $incrementPart; + } + + } + + // Generate the next free $autocompletePartDigits-digit increment + $nextIncrement = 1; // Start at the beginning + + while (in_array($nextIncrement, $usedIncrements, true)) { + $nextIncrement++; + } + + return str_pad((string) $nextIncrement, $suggestPartDigits, '0', STR_PAD_LEFT); + } + + /** + * Generates the next IPN suggestion based on the maximum numeric suffix found in the given IPNs. + * + * The new IPN is constructed using the base format of the first provided IPN, + * incremented by the next free numeric suffix. If no base IPNs are found, + * returns null. + * + * @param array $givenIpns List of IPNs to analyze. + * + * @return string|null The next suggested IPN, or null if no base IPNs can be derived. + */ + private function getNextIpnSuggestion(array $givenIpns): ?string { + $maxSuffix = 0; + + foreach ($givenIpns as $ipn) { + // Check whether the IPN contains a suffix "_ " + if (preg_match('/_(\d+)$/', $ipn, $matches)) { + $suffix = (int)$matches[1]; + if ($suffix > $maxSuffix) { + $maxSuffix = $suffix; // Höchste Nummer speichern + } + } + } + + // Find the basic format (the IPN without suffix) from the first IPN + $baseIpn = $givenIpns[0] ?? ''; + $baseIpn = preg_replace('/_\d+$/', '', $baseIpn); // Remove existing "_ " + + if ($baseIpn === '') { + return null; + } + + // Generate next free possible IPN + return $baseIpn . '_' . ($maxSuffix + 1); + } + } diff --git a/src/Repository/Parts/CategoryRepository.php b/src/Repository/Parts/CategoryRepository.php index 8e270047..9bbb1e37 100644 --- a/src/Repository/Parts/CategoryRepository.php +++ b/src/Repository/Parts/CategoryRepository.php @@ -28,13 +28,13 @@ use InvalidArgumentException; class CategoryRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof Category) { throw new InvalidArgumentException('$element must be an Category!'); } - return $this->getPartsByField($element, $order_by, 'category'); + return $this->getPartsByField($element, $nameOrderDirection, 'category'); } public function getPartsCount(object $element): int diff --git a/src/Repository/Parts/FootprintRepository.php b/src/Repository/Parts/FootprintRepository.php index 355cb1bb..8934831a 100644 --- a/src/Repository/Parts/FootprintRepository.php +++ b/src/Repository/Parts/FootprintRepository.php @@ -28,13 +28,13 @@ use InvalidArgumentException; class FootprintRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof Footprint) { throw new InvalidArgumentException('$element must be an Footprint!'); } - return $this->getPartsByField($element, $order_by, 'footprint'); + return $this->getPartsByField($element, $nameOrderDirection, 'footprint'); } public function getPartsCount(object $element): int diff --git a/src/Repository/Parts/ManufacturerRepository.php b/src/Repository/Parts/ManufacturerRepository.php index a47142d4..1a838710 100644 --- a/src/Repository/Parts/ManufacturerRepository.php +++ b/src/Repository/Parts/ManufacturerRepository.php @@ -28,13 +28,13 @@ use InvalidArgumentException; class ManufacturerRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof Manufacturer) { throw new InvalidArgumentException('$element must be an Manufacturer!'); } - return $this->getPartsByField($element, $order_by, 'manufacturer'); + return $this->getPartsByField($element, $nameOrderDirection, 'manufacturer'); } public function getPartsCount(object $element): int diff --git a/src/Repository/Parts/MeasurementUnitRepository.php b/src/Repository/Parts/MeasurementUnitRepository.php index 1c9b106b..c581f751 100644 --- a/src/Repository/Parts/MeasurementUnitRepository.php +++ b/src/Repository/Parts/MeasurementUnitRepository.php @@ -28,13 +28,13 @@ use InvalidArgumentException; class MeasurementUnitRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof MeasurementUnit) { throw new InvalidArgumentException('$element must be an MeasurementUnit!'); } - return $this->getPartsByField($element, $order_by, 'partUnit'); + return $this->getPartsByField($element, $nameOrderDirection, 'partUnit'); } public function getPartsCount(object $element): int diff --git a/src/Repository/Parts/PartCustomStateRepository.php b/src/Repository/Parts/PartCustomStateRepository.php new file mode 100644 index 00000000..d66221a2 --- /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/Repository/Parts/StorelocationRepository.php b/src/Repository/Parts/StorelocationRepository.php index e6eea9a5..82317868 100644 --- a/src/Repository/Parts/StorelocationRepository.php +++ b/src/Repository/Parts/StorelocationRepository.php @@ -23,21 +23,16 @@ declare(strict_types=1); namespace App\Repository\Parts; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Repository\AbstractPartsContainingRepository; use Doctrine\ORM\QueryBuilder; use InvalidArgumentException; class StorelocationRepository extends AbstractPartsContainingRepository { - /** - * @param object $element - * @param array $order_by - * @return array - */ - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { - if (!$element instanceof Storelocation) { + if (!$element instanceof StorageLocation) { throw new InvalidArgumentException('$element must be an Storelocation!'); } @@ -47,18 +42,16 @@ class StorelocationRepository extends AbstractPartsContainingRepository ->from(Part::class, 'part') ->leftJoin('part.partLots', 'lots') ->where('lots.storage_location = ?1') - ->setParameter(1, $element); - - foreach ($order_by as $field => $order) { - $qb->addOrderBy('part.'.$field, $order); - } + ->setParameter(1, $element) + ->orderBy('NATSORT(part.name)', $nameOrderDirection) + ; return $qb->getQuery()->getResult(); } public function getPartsCount(object $element): int { - if (!$element instanceof Storelocation) { + if (!$element instanceof StorageLocation) { throw new InvalidArgumentException('$element must be an Storelocation!'); } diff --git a/src/Repository/Parts/SupplierRepository.php b/src/Repository/Parts/SupplierRepository.php index 6dc995f1..393ae593 100644 --- a/src/Repository/Parts/SupplierRepository.php +++ b/src/Repository/Parts/SupplierRepository.php @@ -30,7 +30,7 @@ use InvalidArgumentException; class SupplierRepository extends AbstractPartsContainingRepository { - public function getParts(object $element, array $order_by = ['name' => 'ASC']): array + public function getParts(object $element, string $nameOrderDirection = "ASC"): array { if (!$element instanceof Supplier) { throw new InvalidArgumentException('$element must be an Supplier!'); @@ -42,11 +42,9 @@ class SupplierRepository extends AbstractPartsContainingRepository ->from(Part::class, 'part') ->leftJoin('part.orderdetails', 'orderdetail') ->where('orderdetail.supplier = ?1') - ->setParameter(1, $element); - - foreach ($order_by as $field => $order) { - $qb->addOrderBy('part.'.$field, $order); - } + ->setParameter(1, $element) + ->orderBy('NATSORT(part.name)', $nameOrderDirection) + ; return $qb->getQuery()->getResult(); } diff --git a/src/Repository/StructuralDBElementRepository.php b/src/Repository/StructuralDBElementRepository.php index 978cee20..7fc38671 100644 --- a/src/Repository/StructuralDBElementRepository.php +++ b/src/Repository/StructuralDBElementRepository.php @@ -40,6 +40,28 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit */ private array $new_entity_cache = []; + /** + * Finds all nodes for the given parent node, ordered by name in a natural sort way + * @param AbstractStructuralDBElement|null $parent + * @param string $nameOrdering The ordering of the names. Either ASC or DESC + * @return array + */ + public function findNodesForParent(?AbstractStructuralDBElement $parent, string $nameOrdering = "ASC"): array + { + $qb = $this->createQueryBuilder('e'); + $qb->select('e') + ->orderBy('NATSORT(e.name)', $nameOrdering); + + if ($parent !== null) { + $qb->where('e.parent = :parent') + ->setParameter('parent', $parent); + } else { + $qb->where('e.parent IS NULL'); + } + //@phpstan-ignore-next-line [parent is only defined by the sub classes] + return $qb->getQuery()->getResult(); + } + /** * Finds all nodes without a parent node. They are our root nodes. * @@ -47,7 +69,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit */ public function findRootNodes(): array { - return $this->findBy(['parent' => null], ['name' => 'ASC']); + return $this->findNodesForParent(null); } /** @@ -63,7 +85,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit { $result = []; - $entities = $this->findBy(['parent' => $parent], ['name' => 'ASC']); + $entities = $this->findNodesForParent($parent); foreach ($entities as $entity) { /** @var AbstractStructuralDBElement $entity */ //Make a recursive call to find all children nodes @@ -89,7 +111,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit { $result = []; - $entities = $this->findBy(['parent' => $parent], ['name' => 'ASC']); + $entities = $this->findNodesForParent($parent); $elementIterator = new StructuralDBElementIterator($entities); $recursiveIterator = new RecursiveIteratorIterator($elementIterator, RecursiveIteratorIterator::SELF_FIRST); @@ -129,7 +151,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit } if (null === $entity) { $class = $this->getClassName(); - /** @var AbstractStructuralDBElement $entity */ + /** @var TEntityClass $entity */ $entity = new $class; $entity->setName($name); $entity->setParent($parent); @@ -221,6 +243,14 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit return $result[0]; } + //If the name contains category delimiters like ->, try to find the element by its full path + if (str_contains($name, '->')) { + $tmp = $this->getEntityByPath($name, '->'); + if (count($tmp) > 0) { + return $tmp[count($tmp) - 1]; + } + } + //If we find nothing, return null return null; } @@ -238,12 +268,12 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit //Try to find if we already have an element cached for this name $entity = $this->getNewEntityFromCache($name, null); - if ($entity) { + if ($entity !== null) { return $entity; } $class = $this->getClassName(); - /** @var AbstractStructuralDBElement $entity */ + /** @var TEntityClass $entity */ $entity = new $class; $entity->setName($name); diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index fa95e83d..bbaa2b39 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -23,7 +23,9 @@ declare(strict_types=1); namespace App\Repository; use App\Entity\UserSystem\User; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\Query\Parameter; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -34,6 +36,7 @@ use Symfony\Component\Security\Core\User\UserInterface; * @method User[] findAll() * @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) * @extends NamedDBElementRepository + * @see \App\Tests\Repository\UserRepositoryTest */ final class UserRepository extends NamedDBElementRepository implements PasswordUpgraderInterface { @@ -97,10 +100,8 @@ final class UserRepository extends NamedDBElementRepository implements PasswordU ->where('u.name = (:name)') ->orWhere('u.email = (:email)'); - $qb->setParameters([ - 'email' => $name_or_password, - 'name' => $name_or_password, - ]); + $qb->setParameter('email', $name_or_password); + $qb->setParameter('name', $name_or_password); try { return $qb->getQuery()->getOneOrNullResult(); diff --git a/src/Repository/UserSystem/ApiTokenRepository.php b/src/Repository/UserSystem/ApiTokenRepository.php new file mode 100644 index 00000000..609014f3 --- /dev/null +++ b/src/Repository/UserSystem/ApiTokenRepository.php @@ -0,0 +1,30 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Repository\UserSystem; + +use Doctrine\ORM\EntityRepository; + +class ApiTokenRepository extends EntityRepository +{ +} \ No newline at end of file diff --git a/src/Security/ApiTokenAuthenticatedToken.php b/src/Security/ApiTokenAuthenticatedToken.php new file mode 100644 index 00000000..2f186e63 --- /dev/null +++ b/src/Security/ApiTokenAuthenticatedToken.php @@ -0,0 +1,52 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Security; + +use App\Entity\UserSystem\ApiToken; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken; + +class ApiTokenAuthenticatedToken extends PostAuthenticationToken +{ + public function __construct(UserInterface $user, string $firewallName, array $roles, private readonly ApiToken $apiToken) + { + //Add roles for the API + $roles[] = 'ROLE_API_AUTHENTICATED'; + + //Add roles based on the token level + $roles = array_merge($roles, $apiToken->getLevel()->getAdditionalRoles()); + + + parent::__construct($user, $firewallName, array_unique($roles)); + } + + /** + * Returns the API token that was used to authenticate the user. + * @return ApiToken + */ + public function getApiToken(): ApiToken + { + return $this->apiToken; + } +} \ No newline at end of file diff --git a/src/Security/ApiTokenAuthenticator.php b/src/Security/ApiTokenAuthenticator.php new file mode 100644 index 00000000..a52b1f7c --- /dev/null +++ b/src/Security/ApiTokenAuthenticator.php @@ -0,0 +1,156 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Security; + +use App\Entity\UserSystem\ApiToken; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; +use Symfony\Component\Security\Http\AccessToken\AccessTokenExtractorInterface; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Authenticator similar to the builtin AccessTokenAuthenticator, but we return a Token here which contains information + * about the used token. + */ +class ApiTokenAuthenticator implements AuthenticatorInterface +{ + public function __construct( + #[Autowire(service: 'security.access_token_extractor.main')] + private readonly AccessTokenExtractorInterface $accessTokenExtractor, + private readonly TranslatorInterface $translator, + private readonly EntityManagerInterface $entityManager, + private readonly string $realm = 'api', + ) { + } + + /** + * Gets the ApiToken belonging to the given accessToken string. + * If the token is invalid or expired, an exception is thrown and authentication fails. + * @param string $accessToken + * @return ApiToken + */ + private function getTokenFromString(#[\SensitiveParameter] string $accessToken): ApiToken + { + $repo = $this->entityManager->getRepository(ApiToken::class); + $token = $repo->findOneBy(['token' => $accessToken]); + + if (!$token instanceof ApiToken) { + throw new BadCredentialsException(); + } + + if (!$token->isValid()) { + throw new CustomUserMessageAuthenticationException('Token expired'); + } + + $old_time = $token->getLastTimeUsed(); + //Set the last used date of the token + $token->setLastTimeUsed(new \DateTimeImmutable()); + //Only flush the token if the last used date change is more than 10 minutes + //For performance reasons we don't want to flush the token every time it is used, but only if it is used more than 10 minutes after the last time it was used + //If a flush is later in the code we don't want to flush the token again + if ($old_time === null || $old_time->diff($token->getLastTimeUsed())->i > 10) { + $this->entityManager->flush(); + } + + return $token; + } + + public function supports(Request $request): ?bool + { + return null === $this->accessTokenExtractor->extractAccessToken($request) ? false : null; + } + + public function authenticate(Request $request): Passport + { + $accessToken = $this->accessTokenExtractor->extractAccessToken($request); + if (!$accessToken) { + throw new BadCredentialsException('Invalid credentials.'); + } + + $apiToken = $this->getTokenFromString($accessToken); + $userBadge = new UserBadge($apiToken->getUser()?->getUserIdentifier() ?? throw new BadCredentialsException('Invalid credentials.')); + $apiBadge = new ApiTokenBadge($apiToken); + + return new SelfValidatingPassport($userBadge, [$apiBadge]); + } + + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + return new ApiTokenAuthenticatedToken( + $passport->getUser(), + $firewallName, + $passport->getUser()->getRoles(), + $passport->getBadge(ApiTokenBadge::class)?->getApiToken() ?? throw new \LogicException('Passport does not contain an API token.') + ); + } + + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response + { + $errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), + 'security'); + + return new Response( + null, + Response::HTTP_UNAUTHORIZED, + ['WWW-Authenticate' => $this->getAuthenticateHeader($errorMessage)] + ); + } + + /** + * @see https://datatracker.ietf.org/doc/html/rfc6750#section-3 + */ + private function getAuthenticateHeader(?string $errorDescription = null): string + { + $data = [ + 'realm' => $this->realm, + 'error' => 'invalid_token', + 'error_description' => $errorDescription, + ]; + $values = []; + foreach ($data as $k => $v) { + if (null === $v || '' === $v) { + continue; + } + $values[] = sprintf('%s="%s"', $k, $v); + } + + return sprintf('Bearer %s', implode(',', $values)); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return null; + } +} \ No newline at end of file diff --git a/src/Security/ApiTokenBadge.php b/src/Security/ApiTokenBadge.php new file mode 100644 index 00000000..d2429a06 --- /dev/null +++ b/src/Security/ApiTokenBadge.php @@ -0,0 +1,51 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Security; + +use App\Entity\UserSystem\ApiToken; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; + +class ApiTokenBadge implements BadgeInterface +{ + + /** + * @param ApiToken $apiToken + */ + public function __construct(private readonly ApiToken $apiToken) + { + } + + /** + * @return ApiToken The token that was used to authenticate the user + */ + public function getApiToken(): ApiToken + { + return $this->apiToken; + } + + public function isResolved(): bool + { + return true; + } +} \ No newline at end of file diff --git a/src/Security/AuthenticationEntryPoint.php b/src/Security/AuthenticationEntryPoint.php new file mode 100644 index 00000000..41f624b2 --- /dev/null +++ b/src/Security/AuthenticationEntryPoint.php @@ -0,0 +1,89 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Security; + +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; + +use function Symfony\Component\Translation\t; + +/** + * This class decides, what to do, when a user tries to access a page, that requires authentication and he is not + * authenticated / logged in yet. + * For browser requests, the user is redirected to the login page, for API requests, a 401 response with a JSON encoded + * message is returned. + */ +class AuthenticationEntryPoint implements AuthenticationEntryPointInterface +{ + public function __construct( + private readonly UrlGeneratorInterface $urlGenerator, + ) { + } + + public function start(Request $request, ?AuthenticationException $authException = null): Response + { + //Check if the request is an API request + if ($this->isJSONRequest($request)) { + //If it is, we return a 401 response with a JSON body + return new JsonResponse([ + 'title' => 'Unauthorized', + 'detail' => 'Authentication is required. Please pass a valid API token in the Authorization header.', + ], Response::HTTP_UNAUTHORIZED); + } + + //Otherwise we redirect to the login page + + //Add a nice flash message to make it clear what happened + if ($request->getSession() instanceof Session) { + $request->getSession()->getFlashBag()->add('error', t('login.flash.access_denied_please_login')); + } + + return new RedirectResponse($this->urlGenerator->generate('login')); + } + + private function isJSONRequest(Request $request): bool + { + //If either the content type or accept header is a json type, we assume it is an API request + $contentType = $request->headers->get('Content-Type'); + $accept = $request->headers->get('Accept'); + + $tmp = false; + + if ($contentType !== null) { + $tmp = str_contains($contentType, 'json'); + } + + if ($accept !== null) { + $tmp = $tmp || str_contains($accept, 'json'); + } + + return $tmp; + } +} \ No newline at end of file diff --git a/src/Security/EnsureSAMLUserForSAMLLoginChecker.php b/src/Security/EnsureSAMLUserForSAMLLoginChecker.php index 7b540984..0ebf893c 100644 --- a/src/Security/EnsureSAMLUserForSAMLLoginChecker.php +++ b/src/Security/EnsureSAMLUserForSAMLLoginChecker.php @@ -25,6 +25,7 @@ namespace App\Security; use App\Entity\UserSystem\User; use Nbgrp\OneloginSamlBundle\Security\Http\Authenticator\Token\SamlToken; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; use Symfony\Contracts\Translation\TranslatorInterface; @@ -50,13 +51,20 @@ class EnsureSAMLUserForSAMLLoginChecker implements EventSubscriberInterface $token = $event->getAuthenticationToken(); $user = $token->getUser(); - //If we are using SAML, we need to check that the user is a SAML user. - if ($token instanceof SamlToken) { - if ($user instanceof User && !$user->isSamlUser()) { - throw new CustomUserMessageAccountStatusException($this->translator->trans('saml.error.cannot_login_local_user_per_saml', [], 'security')); - } - } elseif ($user instanceof User && $user->isSamlUser()) { - //Ensure that you can not login locally with a SAML user (even if this should not happen, as the password is not set) + //Do not check for anonymous users + if (!$user instanceof User) { + return; + } + + //Do not allow SAML users to login as local user + if ($token instanceof SamlToken && !$user->isSamlUser()) { + throw new CustomUserMessageAccountStatusException($this->translator->trans('saml.error.cannot_login_local_user_per_saml', + [], 'security')); + } + + //Do not allow local users to login as SAML user via local username and password + if ($token instanceof UsernamePasswordToken && $user->isSamlUser()) { + //Ensure that you can not login locally with a SAML user (even though this should not happen, as the password is not set) throw new CustomUserMessageAccountStatusException($this->translator->trans('saml.error.cannot_login_saml_user_locally', [], 'security')); } } diff --git a/src/Security/SamlUserFactory.php b/src/Security/SamlUserFactory.php index d5c68146..312be859 100644 --- a/src/Security/SamlUserFactory.php +++ b/src/Security/SamlUserFactory.php @@ -116,10 +116,10 @@ class SamlUserFactory implements SamlUserFactoryInterface, EventSubscriberInterf * 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 new file mode 100644 index 00000000..4d1269d6 --- /dev/null +++ b/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php @@ -0,0 +1,109 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Security\TwoFactor; + +use App\Entity\UserSystem\WebauthnKey; +use Doctrine\ORM\EntityManagerInterface; +use Jbtronics\TFAWebauthn\Services\UserPublicKeyCredentialSourceRepository; +use Jbtronics\TFAWebauthn\Services\WebauthnProvider; +use Scheb\TwoFactorBundle\Security\TwoFactor\AuthenticationContextInterface; +use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorFormRendererInterface; +use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderInterface; +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 + * on the used webauthn key, which can be viewed in the user settings. + */ +#[AsDecorator('jbtronics_webauthn_tfa.two_factor_provider')] +class WebauthnKeyLastUseTwoFactorProvider implements TwoFactorProviderInterface +{ + + public function __construct( + #[AutowireDecorated] + private readonly TwoFactorProviderInterface $decorated, + private readonly EntityManagerInterface $entityManager, + #[Autowire(service: 'jbtronics_webauthn_tfa.user_public_key_source_repo')] + private readonly UserPublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, + #[Autowire(service: 'jbtronics_webauthn_tfa.webauthn_provider')] + private readonly WebauthnProvider $webauthnProvider, + ) + { + } + + public function beginAuthentication(AuthenticationContextInterface $context): bool + { + return $this->decorated->beginAuthentication($context); + } + + public function prepareAuthentication(object $user): void + { + $this->decorated->prepareAuthentication($user); + } + + public function validateAuthenticationCode(object $user, string $authenticationCode): bool + { + //Try to extract the used webauthn key from the code + $webauthnKey = $this->getWebauthnKeyFromCode($authenticationCode); + + //Perform the actual validation like normal + $tmp = $this->decorated->validateAuthenticationCode($user, $authenticationCode); + + //Update the last used date of the webauthn key, if the validation was successful + if($tmp && $webauthnKey !== null) { + $webauthnKey->updateLastTimeUsed(); + $this->entityManager->flush(); + } + + return $tmp; + } + + public function getFormRenderer(): TwoFactorFormRendererInterface + { + return $this->decorated->getFormRenderer(); + } + + private function getWebauthnKeyFromCode(string $authenticationCode): ?WebauthnKey + { + $serializer = $this->webauthnProvider->getWebauthnSerializer(); + + //Try to load the public key credential from the code + $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); + + //If the credential source is not an instance of WebauthnKey, return null + if(!($publicKeyCredentialSource instanceof WebauthnKey)) { + return null; + } + + return $publicKeyCredentialSource; + } +} diff --git a/src/Security/UserChecker.php b/src/Security/UserChecker.php index fd53a295..239a6096 100644 --- a/src/Security/UserChecker.php +++ b/src/Security/UserChecker.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Security; use App\Entity\UserSystem\User; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AccountStatusException; use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; use Symfony\Component\Security\Core\User\UserCheckerInterface; @@ -40,12 +41,10 @@ final class UserChecker implements UserCheckerInterface /** * Checks the user account before authentication. - * - * @throws AccountStatusException */ public function checkPreAuth(UserInterface $user): void { - // TODO: Implement checkPreAuth() method. + //We don't need to check the user before authentication, just implemented to fulfill the interface } /** @@ -53,7 +52,7 @@ final class UserChecker implements UserCheckerInterface * * @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 0b32188c..df3d73a7 100644 --- a/src/Security/Voter/AttachmentVoter.php +++ b/src/Security/Voter/AttachmentVoter.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Security\Voter; +use App\Entity\Attachments\PartCustomStateAttachment; +use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\Attachment; @@ -34,32 +36,30 @@ use App\Entity\Attachments\ManufacturerAttachment; use App\Entity\Attachments\MeasurementUnitAttachment; use App\Entity\Attachments\PartAttachment; use App\Entity\Attachments\ProjectAttachment; -use App\Entity\Attachments\StorelocationAttachment; +use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Attachments\SupplierAttachment; use App\Entity\Attachments\UserAttachment; -use App\Entity\UserSystem\User; -use App\Services\UserSystem\PermissionManager; -use Doctrine\ORM\EntityManagerInterface; use RuntimeException; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; + use function in_array; -class AttachmentVoter extends ExtendedVoter +/** + * @phpstan-extends Voter + */ +final class AttachmentVoter extends Voter { - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) + private const ALLOWED_ATTRIBUTES = ['read', 'view', 'edit', 'delete', 'create', 'show_private', 'show_history']; + + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) { - parent::__construct($resolver, $entityManager); } - /** - * Similar to voteOnAttribute, but checking for the anonymous user is already done. - * The current user (or the anonymous user) is passed by $user. - * - * @param string $attribute - */ - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { - //return $this->resolver->inherit($user, 'attachments', $attribute) ?? false; //This voter only works for attachments if (!is_a($subject, Attachment::class, true)) { @@ -67,7 +67,8 @@ class AttachmentVoter extends ExtendedVoter } if ($attribute === 'show_private') { - return $this->resolver->inherit($user, 'attachments', 'show_private') ?? false; + $vote?->addReason('User is not allowed to view private attachments.'); + return $this->helper->isGranted($token, 'attachments', 'show_private', $vote); } @@ -99,7 +100,9 @@ class AttachmentVoter extends ExtendedVoter $param = 'measurement_units'; } elseif (is_a($subject, PartAttachment::class, true)) { $param = 'parts'; - } elseif (is_a($subject, StorelocationAttachment::class, true)) { + } 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)) { $param = 'suppliers'; @@ -113,7 +116,8 @@ class AttachmentVoter extends ExtendedVoter throw new RuntimeException('Encountered unknown Parameter type: ' . $subject); } - return $this->resolver->inherit($user, $param, $this->mapOperation($attribute)) ?? false; + $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; @@ -137,14 +141,24 @@ class AttachmentVoter extends ExtendedVoter * * @return bool True if the attribute and subject are supported, false otherwise */ - protected function supports(string $attribute, $subject): bool + protected function supports(string $attribute, mixed $subject): bool { if (is_a($subject, Attachment::class, true)) { //These are the allowed attributes - return in_array($attribute, ['read', 'view', 'edit', 'delete', 'create', 'show_private', 'show_history'], true); + return in_array($attribute, self::ALLOWED_ATTRIBUTES, true); } //Allow class name as subject return false; } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, self::ALLOWED_ATTRIBUTES, true); + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, Attachment::class, true); + } } diff --git a/src/Security/Voter/BOMEntryVoter.php b/src/Security/Voter/BOMEntryVoter.php new file mode 100644 index 00000000..4ce40d47 --- /dev/null +++ b/src/Security/Voter/BOMEntryVoter.php @@ -0,0 +1,91 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Security\Voter; + +use App\Entity\ProjectSystem\Project; +use App\Entity\ProjectSystem\ProjectBOMEntry; +use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; + +/** + * @phpstan-extends Voter + */ +class BOMEntryVoter extends Voter +{ + + private const ALLOWED_ATTRIBUTES = ['read', 'view', 'edit', 'delete', 'create', 'show_history']; + + public function __construct(private readonly Security $security) + { + } + + 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, ?Vote $vote = null): bool + { + if (!is_a($subject, ProjectBOMEntry::class, true)) { + return false; + } + + if (is_object($subject)) { + $project = $subject->getProject(); + + //Allow everything if the project was not set yet + if ($project === null) { + return true; + } + } else { + //If a string was given, use the general project permissions to resolve permissions + $project = Project::class; + } + + //Entry can be read if the user has read access to the project + if ($attribute === 'read') { + return $this->security->isGranted('read', $project); + } + + //History can be shown if the user has show_history access to the project + if ($attribute === 'show_history') { + return $this->security->isGranted('show_history', $project); + } + + //Everything else can be done if the user has edit access to the project + return $this->security->isGranted('edit', $project); + } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, self::ALLOWED_ATTRIBUTES, true); + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, ProjectBOMEntry::class, true); + } +} diff --git a/src/Security/Voter/ExtendedVoter.php b/src/Security/Voter/ExtendedVoter.php deleted file mode 100644 index 279944fc..00000000 --- a/src/Security/Voter/ExtendedVoter.php +++ /dev/null @@ -1,68 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Security\Voter; - -use App\Entity\UserSystem\User; -use App\Repository\UserRepository; -use App\Services\UserSystem\PermissionManager; -use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Authorization\Voter\Voter; - -/** - * The purpose of this class is, to use the anonymous user from DB in the case, that nobody is logged in. - */ -abstract class ExtendedVoter extends Voter -{ - public function __construct(protected PermissionManager $resolver, protected EntityManagerInterface $entityManager) - { - } - - final protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool - { - $user = $token->getUser(); - - //An allowed user is not allowed to do anything... - if ($user instanceof User && $user->isDisabled()) { - return false; - } - - // if the user is anonymous (meaning $user is null), we use the anonymous user. - if (!$user instanceof User) { - /** @var UserRepository $repo */ - $repo = $this->entityManager->getRepository(User::class); - $user = $repo->getAnonymousUser(); - if (!$user instanceof User) { - return false; - } - } - - return $this->voteOnUser($attribute, $subject, $user); - } - - /** - * Similar to voteOnAttribute, but checking for the anonymous user is already done. - * The current user (or the anonymous user) is passed by $user. - */ - abstract protected function voteOnUser(string $attribute, $subject, User $user): bool; -} diff --git a/src/Security/Voter/GroupVoter.php b/src/Security/Voter/GroupVoter.php index e1e21543..f2ce6953 100644 --- a/src/Security/Voter/GroupVoter.php +++ b/src/Security/Voter/GroupVoter.php @@ -23,19 +23,30 @@ declare(strict_types=1); namespace App\Security\Voter; use App\Entity\UserSystem\Group; -use App\Entity\UserSystem\User; +use App\Services\UserSystem\VoterHelper; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class GroupVoter extends ExtendedVoter +/** + * @phpstan-extends Voter + */ +final class GroupVoter extends Voter { + + public function __construct(private readonly VoterHelper $helper) + { + } + /** * Similar to voteOnAttribute, but checking for the anonymous user is already done. * The current user (or the anonymous user) is passed by $user. * * @param string $attribute */ - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { - return $this->resolver->inherit($user, 'groups', $attribute) ?? false; + return $this->helper->isGranted($token, 'groups', $attribute, $vote); } /** @@ -46,12 +57,22 @@ class GroupVoter extends ExtendedVoter * * @return bool True if the attribute and subject are supported, false otherwise */ - protected function supports(string $attribute, $subject): bool + protected function supports(string $attribute, mixed $subject): bool { if (is_a($subject, Group::class, true)) { - return $this->resolver->isValidOperation('groups', $attribute); + return $this->helper->isValidOperation('groups', $attribute); } return false; } + + public function supportsAttribute(string $attribute): bool + { + return $this->helper->isValidOperation('groups', $attribute); + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, Group::class, true); + } } diff --git a/src/Security/Voter/HasAccessPermissionsVoter.php b/src/Security/Voter/HasAccessPermissionsVoter.php index 3ea6dd36..9adef977 100644 --- a/src/Security/Voter/HasAccessPermissionsVoter.php +++ b/src/Security/Voter/HasAccessPermissionsVoter.php @@ -23,23 +23,38 @@ declare(strict_types=1); namespace App\Security\Voter; -use App\Entity\UserSystem\User; +use App\Services\UserSystem\PermissionManager; +use App\Services\UserSystem\VoterHelper; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** * This voter implements a virtual role, which can be used if the user has any permission set to allowed. * We use this to restrict access to the homepage. + * @phpstan-extends Voter */ -class HasAccessPermissionsVoter extends ExtendedVoter +final class HasAccessPermissionsVoter extends Voter { public const ROLE = "HAS_ACCESS_PERMISSIONS"; - protected function voteOnUser(string $attribute, $subject, User $user): bool + public function __construct(private readonly PermissionManager $permissionManager, private readonly VoterHelper $helper) { - return $this->resolver->hasAnyPermissionSetToAllowInherited($user); + } + + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool + { + $user = $this->helper->resolveUser($token); + return $this->permissionManager->hasAnyPermissionSetToAllowInherited($user); } protected function supports(string $attribute, mixed $subject): bool { return $attribute === self::ROLE; } -} \ No newline at end of file + + public function supportsAttribute(string $attribute): bool + { + return $attribute === self::ROLE; + } +} diff --git a/src/Security/Voter/ImpersonateUserVoter.php b/src/Security/Voter/ImpersonateUserVoter.php index f1392568..1f8a70c6 100644 --- a/src/Security/Voter/ImpersonateUserVoter.php +++ b/src/Security/Voter/ImpersonateUserVoter.php @@ -24,37 +24,49 @@ declare(strict_types=1); namespace App\Security\Voter; use App\Entity\UserSystem\User; -use App\Services\UserSystem\PermissionManager; +use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; -class ImpersonateUserVoter extends Voter +/** + * This voter implements a virtual role, which can be used if the user has any permission set to allowed. + * We use this to restrict access to the homepage. + * @phpstan-extends Voter + */ +final class ImpersonateUserVoter extends Voter { - public function __construct(private PermissionManager $permissionManager) + public function __construct(private readonly VoterHelper $helper) { } protected function supports(string $attribute, mixed $subject): bool { - return $attribute == 'CAN_SWITCH_USER' + return $attribute === 'CAN_SWITCH_USER' && $subject instanceof UserInterface; } - protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool { - $user = $token->getUser(); + $result = $this->helper->isGranted($token, 'users', 'impersonate'); - if (!$user instanceof User || !$subject instanceof UserInterface) { - return false; + if ($result === false) { + $vote?->addReason('User is not allowed to impersonate other users.'); + $this->helper->addReason($vote, 'users', 'impersonate'); } - //An disabled user is not allowed to do anything... - if ($user->isDisabled()) { - return false; - } - - return $this->permissionManager->inherit($user, 'users', 'impersonate') ?? false; + return $result; } -} \ No newline at end of file + + public function supportsAttribute(string $attribute): bool + { + return $attribute === 'CAN_SWITCH_USER'; + } + + public function supportsType(string $subjectType): bool + { + return is_a($subjectType, User::class, true); + } +} diff --git a/src/Security/Voter/LabelProfileVoter.php b/src/Security/Voter/LabelProfileVoter.php index 5a3699a2..1687bf45 100644 --- a/src/Security/Voter/LabelProfileVoter.php +++ b/src/Security/Voter/LabelProfileVoter.php @@ -42,9 +42,15 @@ declare(strict_types=1); namespace App\Security\Voter; use App\Entity\LabelSystem\LabelProfile; -use App\Entity\UserSystem\User; +use App\Services\UserSystem\VoterHelper; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class LabelProfileVoter extends ExtendedVoter +/** + * @phpstan-extends Voter + */ +final class LabelProfileVoter extends Voter { protected const MAPPING = [ 'read' => 'read_profiles', @@ -53,23 +59,37 @@ class LabelProfileVoter extends ExtendedVoter 'delete' => 'delete_profiles', 'show_history' => 'show_history', 'revert_element' => 'revert_element', + 'import' => 'import', ]; - protected function voteOnUser(string $attribute, $subject, User $user): bool + public function __construct(private readonly VoterHelper $helper) + {} + + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { - return $this->resolver->inherit($user, 'labels', self::MAPPING[$attribute]) ?? false; + return $this->helper->isGranted($token, 'labels', self::MAPPING[$attribute], $vote); } protected function supports($attribute, $subject): bool { - if ($subject instanceof LabelProfile) { + if (is_a($subject, LabelProfile::class, true)) { if (!isset(self::MAPPING[$attribute])) { return false; } - return $this->resolver->isValidOperation('labels', self::MAPPING[$attribute]); + return $this->helper->isValidOperation('labels', self::MAPPING[$attribute]); } return false; } + + public function supportsAttribute(string $attribute): bool + { + return isset(self::MAPPING[$attribute]); + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, LabelProfile::class, true); + } } diff --git a/src/Security/Voter/LogEntryVoter.php b/src/Security/Voter/LogEntryVoter.php index 20c34863..dcb75a7a 100644 --- a/src/Security/Voter/LogEntryVoter.php +++ b/src/Security/Voter/LogEntryVoter.php @@ -22,41 +22,46 @@ declare(strict_types=1); namespace App\Security\Voter; +use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\LogSystem\AbstractLogEntry; -use App\Entity\UserSystem\User; -use App\Services\UserSystem\PermissionManager; -use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class LogEntryVoter extends ExtendedVoter +/** + * @phpstan-extends Voter + */ +final class LogEntryVoter extends Voter { final public const ALLOWED_OPS = ['read', 'show_details', 'delete']; - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, private readonly Security $security) + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) { - parent::__construct($resolver, $entityManager); } - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { + $user = $this->helper->resolveUser($token); + if (!$subject instanceof AbstractLogEntry) { throw new \InvalidArgumentException('The subject must be an instance of '.AbstractLogEntry::class); } if ('delete' === $attribute) { - return $this->resolver->inherit($user, 'system', 'delete_logs') ?? false; + return $this->helper->isGranted($token, 'system', 'delete_logs', $vote); } if ('read' === $attribute) { //Allow read of the users own log entries if ( $subject->getUser() === $user - && $this->resolver->inherit($user, 'self', 'show_logs') + && $this->helper->isGranted($token, 'self', 'show_logs', $vote) ) { return true; } - return $this->resolver->inherit($user, 'system', 'show_logs') ?? false; + return $this->helper->isGranted($token, 'system', 'show_logs', $vote); } if ('show_details' === $attribute) { @@ -67,7 +72,7 @@ class LogEntryVoter extends ExtendedVoter } //In other cases, this behaves like the read permission - return $this->voteOnUser('read', $subject, $user); + return $this->voteOnAttribute('read', $subject, $token); } return false; @@ -81,4 +86,14 @@ class LogEntryVoter extends ExtendedVoter return false; } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, static::ALLOWED_OPS, true); + } + + public function supportsType(string $subjectType): bool + { + return is_a($subjectType, AbstractLogEntry::class, true); + } } diff --git a/src/Security/Voter/OrderdetailVoter.php b/src/Security/Voter/OrderdetailVoter.php index 96ff609e..3bb2a3a3 100644 --- a/src/Security/Voter/OrderdetailVoter.php +++ b/src/Security/Voter/OrderdetailVoter.php @@ -41,23 +41,26 @@ declare(strict_types=1); namespace App\Security\Voter; +use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Parts\Part; use App\Entity\PriceInformations\Orderdetail; -use App\Entity\UserSystem\User; -use App\Services\UserSystem\PermissionManager; -use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class OrderdetailVoter extends ExtendedVoter +/** + * @phpstan-extends Voter + */ +final class OrderdetailVoter extends Voter { - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) { - parent::__construct($resolver, $entityManager); } protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element']; - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { if (! is_a($subject, Orderdetail::class, true)) { throw new \RuntimeException('This voter can only handle Orderdetail objects!'); @@ -73,7 +76,7 @@ class OrderdetailVoter extends ExtendedVoter //If we have no part associated use the generic part permission if (is_string($subject) || !$subject->getPart() instanceof Part) { - return $this->resolver->inherit($user, 'parts', $operation) ?? false; + return $this->helper->isGranted($token, 'parts', $operation, $vote); } //Otherwise vote on the part @@ -88,4 +91,14 @@ class OrderdetailVoter extends ExtendedVoter return false; } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, self::ALLOWED_PERMS, true); + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, Orderdetail::class, true); + } } diff --git a/src/Security/Voter/ParameterVoter.php b/src/Security/Voter/ParameterVoter.php index 6f752518..5dc30ea2 100644 --- a/src/Security/Voter/ParameterVoter.php +++ b/src/Security/Voter/ParameterVoter.php @@ -22,6 +22,8 @@ declare(strict_types=1); */ namespace App\Security\Voter; +use App\Entity\Parameters\PartCustomStateParameter; +use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractDBElement; use App\Entity\Parameters\AbstractParameter; @@ -34,22 +36,26 @@ use App\Entity\Parameters\GroupParameter; use App\Entity\Parameters\ManufacturerParameter; use App\Entity\Parameters\MeasurementUnitParameter; use App\Entity\Parameters\PartParameter; -use App\Entity\Parameters\StorelocationParameter; +use App\Entity\Parameters\StorageLocationParameter; use App\Entity\Parameters\SupplierParameter; -use App\Entity\UserSystem\User; -use App\Services\UserSystem\PermissionManager; -use Doctrine\ORM\EntityManagerInterface; 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; -class ParameterVoter extends ExtendedVoter +/** + * @phpstan-extends Voter + */ +final class ParameterVoter extends Voter { - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) + private const ALLOWED_ATTRIBUTES = ['read', 'edit', 'delete', 'create', 'show_history', 'revert_element']; + + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) { - parent::__construct($resolver, $entityManager); } - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { //return $this->resolver->inherit($user, 'attachments', $attribute) ?? false; @@ -92,7 +98,9 @@ class ParameterVoter extends ExtendedVoter $param = 'measurement_units'; } elseif (is_a($subject, PartParameter::class, true)) { $param = 'parts'; - } elseif (is_a($subject, StorelocationParameter::class, true)) { + } 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)) { $param = 'suppliers'; @@ -104,17 +112,28 @@ class ParameterVoter extends ExtendedVoter throw new RuntimeException('Encountered unknown Parameter type: ' . (is_object($subject) ? $subject::class : $subject)); } - return $this->resolver->inherit($user, $param, $attribute) ?? false; + return $this->helper->isGranted($token, $param, $attribute, $vote); } protected function supports(string $attribute, $subject): bool { if (is_a($subject, AbstractParameter::class, true)) { //These are the allowed attributes - return in_array($attribute, ['read', 'edit', 'delete', 'create', 'show_history', 'revert_element'], true); + return in_array($attribute, self::ALLOWED_ATTRIBUTES, true); } //Allow class name as subject return false; } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, self::ALLOWED_ATTRIBUTES, true); + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, AbstractParameter::class, true); + } + } diff --git a/src/Security/Voter/PartAssociationVoter.php b/src/Security/Voter/PartAssociationVoter.php new file mode 100644 index 00000000..f1eb83c7 --- /dev/null +++ b/src/Security/Voter/PartAssociationVoter.php @@ -0,0 +1,106 @@ +. + */ + +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\Security\Voter; + +use App\Entity\Parts\PartAssociation; +use App\Services\UserSystem\VoterHelper; +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; + +/** + * This voter handles permissions for part associations. + * The permissions are inherited from the part. + * @phpstan-extends Voter + */ +final class PartAssociationVoter extends Voter +{ + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) + { + } + + protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element']; + + 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!'); + } + + $operation = match ($attribute) { + 'read' => 'read', + 'edit', 'create', 'delete' => 'edit', + 'show_history' => 'show_history', + 'revert_element' => 'revert_element', + default => throw new \RuntimeException('Encountered unknown operation "'.$attribute.'"!'), + }; + + //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, $vote); + } + + //Otherwise vote on the part + return $this->security->isGranted($attribute, $subject->getOwner()); + } + + protected function supports($attribute, $subject): bool + { + if (is_a($subject, PartAssociation::class, true)) { + return in_array($attribute, self::ALLOWED_PERMS, true); + } + + return false; + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, PartAssociation::class, true); + } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, self::ALLOWED_PERMS, true); + } +} diff --git a/src/Security/Voter/PartLotVoter.php b/src/Security/Voter/PartLotVoter.php index c9ddb89e..87c3d135 100644 --- a/src/Security/Voter/PartLotVoter.php +++ b/src/Security/Voter/PartLotVoter.php @@ -41,31 +41,32 @@ declare(strict_types=1); namespace App\Security\Voter; +use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\UserSystem\User; -use App\Services\UserSystem\PermissionManager; -use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class PartLotVoter extends ExtendedVoter +/** + * @phpstan-extends Voter + */ +final class PartLotVoter extends Voter { - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) { - parent::__construct($resolver, $entityManager); } protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element', 'withdraw', 'add', 'move']; - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { - if (! is_a($subject, PartLot::class, true)) { - throw new \RuntimeException('This voter can only handle PartLot objects!'); - } + $user = $this->helper->resolveUser($token); if (in_array($attribute, ['withdraw', 'add', 'move'], true)) { - $base_permission = $this->resolver->inherit($user, 'parts_stock', $attribute) ?? false; + $base_permission = $this->helper->isGranted($token, 'parts_stock', $attribute, $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 @@ class PartLotVoter extends ExtendedVoter $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 @@ class PartLotVoter extends ExtendedVoter //If we have no part associated use the generic part permission if (is_string($subject) || !$subject->getPart() instanceof Part) { - return $this->resolver->inherit($user, 'parts', $operation) ?? false; + return $this->helper->isGranted($token, 'parts', $operation, $vote); } //Otherwise vote on the part @@ -101,4 +106,14 @@ class PartLotVoter extends ExtendedVoter return false; } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, self::ALLOWED_PERMS, true); + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, PartLot::class, true); + } } diff --git a/src/Security/Voter/PartVoter.php b/src/Security/Voter/PartVoter.php index 878fc6a4..159e6893 100644 --- a/src/Security/Voter/PartVoter.php +++ b/src/Security/Voter/PartVoter.php @@ -23,30 +23,48 @@ declare(strict_types=1); namespace App\Security\Voter; use App\Entity\Parts\Part; -use App\Entity\UserSystem\User; +use App\Services\UserSystem\VoterHelper; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** * A Voter that votes on Part entities. * * See parts permissions for valid operations. + * + * @phpstan-extends Voter */ -class PartVoter extends ExtendedVoter +final class PartVoter extends Voter { final public const READ = 'read'; + public function __construct(private readonly VoterHelper $helper) + { + } + protected function supports($attribute, $subject): bool { if (is_a($subject, Part::class, true)) { - return $this->resolver->isValidOperation('parts', $attribute); + return $this->helper->isValidOperation('parts', $attribute); } //Allow class name as subject return false; } - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { - //Null concealing operator means, that no - return $this->resolver->inherit($user, 'parts', $attribute) ?? false; + return $this->helper->isGranted($token, 'parts', $attribute, $vote); + } + + public function supportsAttribute(string $attribute): bool + { + return $this->helper->isValidOperation('parts', $attribute); + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, Part::class, true); } } diff --git a/src/Security/Voter/PermissionVoter.php b/src/Security/Voter/PermissionVoter.php index 018c4f92..8c304d86 100644 --- a/src/Security/Voter/PermissionVoter.php +++ b/src/Security/Voter/PermissionVoter.php @@ -22,27 +22,41 @@ declare(strict_types=1); namespace App\Security\Voter; -use App\Entity\UserSystem\User; +use App\Services\UserSystem\VoterHelper; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** * This voter allows you to directly check permissions from the permission structure, without passing an object. * This use the syntax like "@permission.op" * However you should use the "normal" object based voters if possible, because they are needed for a future ACL system. + * @phpstan-extends Voter */ -class PermissionVoter extends ExtendedVoter +final class PermissionVoter extends Voter { - /** - * Similar to voteOnAttribute, but checking for the anonymous user is already done. - * The current user (or the anonymous user) is passed by $user. - * - * @param string $attribute - */ - protected function voteOnUser(string $attribute, $subject, User $user): bool + public function __construct(private readonly VoterHelper $helper) + { + + } + + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool { $attribute = ltrim($attribute, '@'); [$perm, $op] = explode('.', $attribute); - return $this->resolver->inherit($user, $perm, $op) ?? false; + $result = $this->helper->isGranted($token, $perm, $op); + if ($result === false) { + $this->helper->addReason($vote, $perm, $op); + } + + return $result; + } + + public function supportsAttribute(string $attribute): bool + { + //Check if the attribute has the form '@permission.operation' + return preg_match('#^@\\w+\\.\\w+$#', $attribute) === 1; } /** @@ -53,14 +67,14 @@ class PermissionVoter extends ExtendedVoter * * @return bool True if the attribute and subject are supported, false otherwise */ - protected function supports(string $attribute, $subject): bool + protected function supports(string $attribute, mixed $subject): bool { //Check if the attribute has the form @permission.operation if (preg_match('#^@\\w+\\.\\w+$#', $attribute)) { $attribute = ltrim($attribute, '@'); [$perm, $op] = explode('.', $attribute); - $valid = $this->resolver->isValidOperation($perm, $op); + $valid = $this->helper->isValidOperation($perm, $op); //if an invalid operation is encountered, throw an exception so the developer knows it if(!$valid) { diff --git a/src/Security/Voter/PricedetailVoter.php b/src/Security/Voter/PricedetailVoter.php index c4def709..ca86f1ce 100644 --- a/src/Security/Voter/PricedetailVoter.php +++ b/src/Security/Voter/PricedetailVoter.php @@ -41,29 +41,28 @@ declare(strict_types=1); namespace App\Security\Voter; +use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\PriceInformations\Orderdetail; use App\Entity\Parts\Part; use App\Entity\PriceInformations\Pricedetail; -use App\Entity\UserSystem\User; -use App\Services\UserSystem\PermissionManager; -use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class PricedetailVoter extends ExtendedVoter +/** + * @phpstan-extends Voter + */ +final class PricedetailVoter extends Voter { - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) { - parent::__construct($resolver, $entityManager); } protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element']; - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { - if (!is_a($subject, Pricedetail::class, true)) { - throw new \RuntimeException('This voter can only handle Pricedetails objects!'); - } - $operation = match ($attribute) { 'read' => 'read', 'edit', 'create', 'delete' => 'edit', @@ -74,7 +73,7 @@ class PricedetailVoter extends ExtendedVoter //If we have no part associated use the generic part permission if (is_string($subject) || !$subject->getOrderdetail() instanceof Orderdetail || !$subject->getOrderdetail()->getPart() instanceof Part) { - return $this->resolver->inherit($user, 'parts', $operation) ?? false; + return $this->helper->isGranted($token, 'parts', $operation, $vote); } //Otherwise vote on the part @@ -89,4 +88,14 @@ class PricedetailVoter extends ExtendedVoter return false; } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, Pricedetail::class, true); + } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, self::ALLOWED_PERMS, true); + } } diff --git a/src/Security/Voter/StructureVoter.php b/src/Security/Voter/StructureVoter.php index 79cef811..16d38e05 100644 --- a/src/Security/Voter/StructureVoter.php +++ b/src/Security/Voter/StructureVoter.php @@ -23,19 +23,26 @@ declare(strict_types=1); namespace App\Security\Voter; use App\Entity\Attachments\AttachmentType; +use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; -use App\Entity\UserSystem\User; -use function get_class; +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; -class StructureVoter extends ExtendedVoter +/** + * @phpstan-extends Voter + */ +final class StructureVoter extends Voter { protected const OBJ_PERM_MAP = [ AttachmentType::class => 'attachment_types', @@ -43,12 +50,17 @@ class StructureVoter extends ExtendedVoter Project::class => 'projects', Footprint::class => 'footprints', Manufacturer::class => 'manufacturers', - Storelocation::class => 'storelocations', + StorageLocation::class => 'storelocations', Supplier::class => 'suppliers', Currency::class => 'currencies', MeasurementUnit::class => 'measurement_units', + PartCustomState::class => 'part_custom_states', ]; + public function __construct(private readonly VoterHelper $helper) + { + } + /** * Determines if the attribute and subject are supported by this voter. * @@ -57,25 +69,30 @@ class StructureVoter extends ExtendedVoter * * @return bool True if the attribute and subject are supported, false otherwise */ - protected function supports(string $attribute, $subject): bool + protected function supports(string $attribute, mixed $subject): bool { if (is_object($subject) || is_string($subject)) { $permission_name = $this->instanceToPermissionName($subject); //If permission name is null, then the subject is not supported - return (null !== $permission_name) && $this->resolver->isValidOperation($permission_name, $attribute); + return (null !== $permission_name) && $this->helper->isValidOperation($permission_name, $attribute); } return false; } + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || $this->instanceToPermissionName($subjectType) !== null; + } + /** * Maps an instance type to the permission name. * - * @param object|string $subject The subject for which the permission name should be generated + * @param object|string $subject The subject for which the permission name should be generated * * @return string|null the name of the permission for the subject's type or null, if the subject is not supported */ - protected function instanceToPermissionName($subject): ?string + protected function instanceToPermissionName(object|string $subject): ?string { $class_name = is_string($subject) ? $subject : $subject::class; @@ -99,10 +116,10 @@ class StructureVoter extends ExtendedVoter * * @param string $attribute */ - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { $permission_name = $this->instanceToPermissionName($subject); //Just resolve the permission - return $this->resolver->inherit($user, $permission_name, $attribute) ?? false; + return $this->helper->isGranted($token, $permission_name, $attribute, $vote); } } diff --git a/src/Security/Voter/UserVoter.php b/src/Security/Voter/UserVoter.php index e98e1701..97f8e4fb 100644 --- a/src/Security/Voter/UserVoter.php +++ b/src/Security/Voter/UserVoter.php @@ -23,10 +23,23 @@ declare(strict_types=1); namespace App\Security\Voter; use App\Entity\UserSystem\User; +use App\Services\UserSystem\PermissionManager; +use App\Services\UserSystem\VoterHelper; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; + use function in_array; -class UserVoter extends ExtendedVoter +/** + * @phpstan-extends Voter + */ +final class UserVoter extends Voter { + public function __construct(private readonly VoterHelper $helper, private readonly PermissionManager $resolver) + { + } + /** * Determines if the attribute and subject are supported by this voter. * @@ -35,7 +48,7 @@ class UserVoter extends ExtendedVoter * * @return bool True if the attribute and subject are supported, false otherwise */ - protected function supports(string $attribute, $subject): bool + protected function supports(string $attribute, mixed $subject): bool { if (is_a($subject, User::class, true)) { return in_array($attribute, @@ -51,14 +64,26 @@ class UserVoter extends ExtendedVoter return false; } + public function supportsAttribute(string $attribute): bool + { + return $this->helper->isValidOperation('users', $attribute) || $this->helper->isValidOperation('self', $attribute) || $attribute === 'info'; + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, User::class, true); + } + /** * Similar to voteOnAttribute, but checking for the anonymous user is already done. * The current user (or the anonymous user) is passed by $user. * * @param string $attribute */ - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { + $user = $this->helper->resolveUser($token); + if ($attribute === 'info') { //Every logged-in user (non-anonymous) can see the info pages of other users if (!$user->isAnonymousUser()) { @@ -71,9 +96,9 @@ class UserVoter extends ExtendedVoter //Check if the checked user is the user itself if (($subject instanceof User) && $subject->getID() === $user->getID() && - $this->resolver->isValidOperation('self', $attribute)) { + $this->helper->isValidOperation('self', $attribute)) { //Then we also need to check the self permission - $tmp = $this->resolver->inherit($user, 'self', $attribute) ?? false; + $tmp = $this->helper->isGranted($token, 'self', $attribute, $vote); //But if the self value is not allowed then use just the user value: if ($tmp) { return $tmp; @@ -81,8 +106,8 @@ class UserVoter extends ExtendedVoter } //Else just check user permission: - if ($this->resolver->isValidOperation('users', $attribute)) { - return $this->resolver->inherit($user, 'users', $attribute) ?? false; + if ($this->helper->isValidOperation('users', $attribute)) { + return $this->helper->isGranted($token, 'users', $attribute, $vote); } return false; diff --git a/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php b/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php new file mode 100644 index 00000000..78679214 --- /dev/null +++ b/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php @@ -0,0 +1,124 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Serializer\APIPlatform; + +use ApiPlatform\Metadata\Exception\ResourceClassNotFoundException; +use ApiPlatform\Metadata\IriConverterInterface; +use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use App\Entity\Attachments\Attachment; +use App\Entity\Parameters\AbstractParameter; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; + +/** + * The purpose of this normalizer is to automatically add the _type discriminator field for the Attachment and AbstractParameter classes + * based on the element IRI. + * So that for a request pointing for a part element, an PartAttachment is automatically created. + * This highly improves UX and is the expected behavior. + */ +class DetermineTypeFromElementIRIDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface +{ + private const SUPPORTED_CLASSES = [ + Attachment::class, + AbstractParameter::class + ]; + + use DenormalizerAwareTrait; + + private const ALREADY_CALLED = self::class . '::ALREADY_CALLED'; + + public function __construct(private readonly IriConverterInterface $iriConverter, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory) + { + } + + /** + * This functions add the _type discriminator to the input array if necessary automatically from the given element IRI. + * @param array $input + * @param Operation $operation + * @return array + * @throws ResourceClassNotFoundException + */ + private function addTypeDiscriminatorIfNecessary(array $input, Operation $operation): array + { + + //We only want to modify POST requests + if (!$operation instanceof Post) { + return $input; + } + + + //Ignore if the _type variable is already set + if (isset($input['_type'])) { + return $input; + } + + if (!isset($input['element']) || !is_string($input['element'])) { + return $input; + } + + //Retrieve the element + $element = $this->iriConverter->getResourceFromIri($input['element']); + + //Retrieve the short name of the operation + $type = $this->resourceMetadataCollectionFactory->create($element::class)->getOperation()->getShortName(); + $input['_type'] = $type; + + return $input; + } + + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): object + { + //If we are on API platform, we want to add the type discriminator if necessary + if (!isset($data['_type']) && isset($context['operation'])) { + $data = $this->addTypeDiscriminatorIfNecessary($data, $context['operation']); + } + + $context[self::ALREADY_CALLED] = true; + + return $this->denormalizer->denormalize($data, $type, $format, $context); + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool + { + //Only denormalize if the _type discriminator is not set and the class is supported and we not have already called this function + return !isset($context[self::ALREADY_CALLED]) + && is_array($data) + && !isset($data['_type']) + && in_array($type, self::SUPPORTED_CLASSES, true); + } + + public function getSupportedTypes(?string $format): array + { + $tmp = []; + + foreach (self::SUPPORTED_CLASSES as $class) { + $tmp[$class] = false; + } + + return $tmp; + } +} diff --git a/src/Serializer/APIPlatform/OverrideClassDenormalizer.php b/src/Serializer/APIPlatform/OverrideClassDenormalizer.php new file mode 100644 index 00000000..c8155abc --- /dev/null +++ b/src/Serializer/APIPlatform/OverrideClassDenormalizer.php @@ -0,0 +1,61 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Serializer\APIPlatform; + +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; + +/** + * The idea of this denormalizer is to allow to override the type of the object created using a certain context key. + * This is required to resolve the issue of the serializer/API platform not correctly being able to determine the type + * of the "element" properties of the Attachment and Parameter subclasses. + */ +class OverrideClassDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface +{ + use DenormalizerAwareTrait; + + public const CONTEXT_KEY = '__override_type__'; + + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed + { + //Deserialize the data with the overridden type + $overrideType = $context[self::CONTEXT_KEY]; + unset($context[self::CONTEXT_KEY]); + + return $this->denormalizer->denormalize($data, $overrideType, $format, $context); + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool + { + return isset($context[self::CONTEXT_KEY]); + } + + public function getSupportedTypes(?string $format): array + { + return [ + '*' => false, + ]; + } +} \ No newline at end of file diff --git a/src/Serializer/APIPlatform/SkippableItemNormalizer.php b/src/Serializer/APIPlatform/SkippableItemNormalizer.php new file mode 100644 index 00000000..5568c4cb --- /dev/null +++ b/src/Serializer/APIPlatform/SkippableItemNormalizer.php @@ -0,0 +1,90 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Serializer\APIPlatform; + +use ApiPlatform\Serializer\ItemNormalizer; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; +use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\SerializerInterface; + +/** + * This class decorates API Platform's ItemNormalizer to allow skipping the normalization process by setting the + * DISABLE_ITEM_NORMALIZER context key to true. This is useful for all kind of serialization operations, where the API + * Platform subsystem should not be used. + */ +#[AsDecorator("api_platform.serializer.normalizer.item")] +class SkippableItemNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface +{ + + public const DISABLE_ITEM_NORMALIZER = 'DISABLE_ITEM_NORMALIZER'; + + public function __construct(private readonly ItemNormalizer $inner) + { + + } + + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed + { + return $this->inner->denormalize($data, $type, $format, $context); + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool + { + if ($context[self::DISABLE_ITEM_NORMALIZER] ?? false) { + return false; + } + + return $this->inner->supportsDenormalization($data, $type, $format, $context); + } + + public function normalize(mixed $object, ?string $format = null, array $context = []): float|int|bool|\ArrayObject|array|string|null + { + return $this->inner->normalize($object, $format, $context); + } + + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool + { + if ($context[self::DISABLE_ITEM_NORMALIZER] ?? false) { + return false; + } + + return $this->inner->supportsNormalization($data, $format, $context); + } + + public function setSerializer(SerializerInterface $serializer): void + { + $this->inner->setSerializer($serializer); + } + + public function getSupportedTypes(?string $format): array + { + //Don't cache results, as we check for the context + return [ + 'object' => false + ]; + } +} \ No newline at end of file diff --git a/src/Serializer/AttachmentNormalizer.php b/src/Serializer/AttachmentNormalizer.php new file mode 100644 index 00000000..bd791d04 --- /dev/null +++ b/src/Serializer/AttachmentNormalizer.php @@ -0,0 +1,84 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Serializer; + +use App\Entity\Attachments\Attachment; +use App\Services\Attachments\AttachmentURLGenerator; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; + +class AttachmentNormalizer implements NormalizerInterface, NormalizerAwareInterface +{ + + use NormalizerAwareTrait; + + private const ALREADY_CALLED = 'ATTACHMENT_NORMALIZER_ALREADY_CALLED'; + + public function __construct( + private readonly AttachmentURLGenerator $attachmentURLGenerator, + ) + { + } + + 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!'); + } + + //Prevent loops, by adding a flag to the context + $context[self::ALREADY_CALLED] = true; + + $data = $this->normalizer->normalize($object, $format, $context); + $data['internal_path'] = $this->attachmentURLGenerator->getInternalViewURL($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 + { + // avoid recursion: only call once per object + if (isset($context[self::ALREADY_CALLED])) { + return false; + } + + return $data instanceof Attachment; + } + + public function getSupportedTypes(?string $format): array + { + return [ + //We depend on the context to determine if we should normalize or not + Attachment::class => false, + ]; + } +} \ No newline at end of file diff --git a/src/Serializer/BigNumberNormalizer.php b/src/Serializer/BigNumberNormalizer.php index 8bb686ee..10cedfa5 100644 --- a/src/Serializer/BigNumberNormalizer.php +++ b/src/Serializer/BigNumberNormalizer.php @@ -22,21 +22,23 @@ declare(strict_types=1); */ namespace App\Serializer; +use Brick\Math\BigDecimal; use Brick\Math\BigNumber; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** * @see \App\Tests\Serializer\BigNumberNormalizerTest */ -class BigNumberNormalizer implements NormalizerInterface +class BigNumberNormalizer implements NormalizerInterface, DenormalizerInterface { - public function supportsNormalization($data, string $format = null, array $context = []): bool + 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!'); @@ -52,6 +54,22 @@ class BigNumberNormalizer implements NormalizerInterface { return [ BigNumber::class => true, + BigDecimal::class => true, ]; } + + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): BigNumber|null + { + if (!is_a($type, BigNumber::class, true)) { + throw new \InvalidArgumentException('This normalizer only supports BigNumber objects!'); + } + + return $type::of($data); + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, 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 b453a58e..775df77f 100644 --- a/src/Serializer/PartNormalizer.php +++ b/src/Serializer/PartNormalizer.php @@ -24,25 +24,29 @@ namespace App\Serializer; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Pricedetail; use Brick\Math\BigDecimal; -use Symfony\Component\DependencyInjection\Attribute\Autowire; -use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; 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\PartNormalizerTest */ -class PartNormalizer implements NormalizerInterface, DenormalizerInterface +class PartNormalizer implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface, DenormalizerAwareInterface { + use NormalizerAwareTrait; + use DenormalizerAwareTrait; + + private const ALREADY_CALLED = 'PART_NORMALIZER_ALREADY_CALLED'; + private const DENORMALIZE_KEY_MAPPING = [ 'notes' => 'comment', 'quantity' => 'instock', @@ -55,30 +59,29 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface public function __construct( private readonly StructuralElementFromNameDenormalizer $locationDenormalizer, - #[Autowire(service: ObjectNormalizer::class)] - private readonly NormalizerInterface $normalizer, - #[Autowire(service: ObjectNormalizer::class)] - private readonly DenormalizerInterface $denormalizer, ) { } - public function supportsNormalization($data, string $format = null, array $context = []): bool + public function supportsNormalization($data, ?string $format = null, array $context = []): bool { - return $data instanceof Part; + //We only remove the type field for CSV export + return !isset($context[self::ALREADY_CALLED]) && $format === 'csv' && $data instanceof Part ; } - /** - * @return (float|mixed)[]|\ArrayObject|null|scalar - * - * @psalm-return \ArrayObject|array{total_instock: float|mixed,...}|null|scalar - */ - public function normalize($object, string $format = null, array $context = []) + public function normalize($object, ?string $format = null, array $context = []): array { if (!$object instanceof Part) { throw new \InvalidArgumentException('This normalizer only supports Part objects!'); } + $context[self::ALREADY_CALLED] = true; + + //Prevent exception in API Platform + if ($object->getID() === null) { + $context['iri'] = 'not-persisted'; + } + $data = $this->normalizer->normalize($object, $format, $context); //Remove type field for CSV export @@ -86,14 +89,19 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface unset($data['type']); } - $data['total_instock'] = $object->getAmountSum(); - 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 { - return is_array($data) && is_a($type, Part::class, true); + //Only denormalize if we are doing a file import operation + if (!($context['partdb_import'] ?? false)) { + return false; + } + + //Only make the denormalizer available on import operations + return !isset($context[self::ALREADY_CALLED]) + && is_array($data) && is_a($type, Part::class, true); } private function normalizeKeys(array &$data): array @@ -109,7 +117,7 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface 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); @@ -129,6 +137,8 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface $data['minamount'] = 0.0; } + $context[self::ALREADY_CALLED] = true; + $object = $this->denormalizer->denormalize($data, $type, $format, $context); if (!$object instanceof Part) { @@ -148,7 +158,7 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface } if (isset($data['storelocation']) && $data['storelocation'] !== "") { - $location = $this->locationDenormalizer->denormalize($data['storelocation'], Storelocation::class, $format, $context); + $location = $this->locationDenormalizer->denormalize($data['storelocation'], StorageLocation::class, $format, $context); $partLot->setStorageLocation($location); } @@ -158,7 +168,7 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface if (isset($data['supplier']) && $data['supplier'] !== "") { $supplier = $this->locationDenormalizer->denormalize($data['supplier'], Supplier::class, $format, $context); - if ($supplier) { + if ($supplier !== null) { $orderdetail = new Orderdetail(); $orderdetail->setSupplier($supplier); diff --git a/src/Serializer/StructuralElementDenormalizer.php b/src/Serializer/StructuralElementDenormalizer.php index ce6f91ca..9f4256f9 100644 --- a/src/Serializer/StructuralElementDenormalizer.php +++ b/src/Serializer/StructuralElementDenormalizer.php @@ -24,9 +24,9 @@ namespace App\Serializer; use App\Entity\Base\AbstractStructuralDBElement; use App\Repository\StructuralDBElementRepository; +use App\Serializer\APIPlatform\SkippableItemNormalizer; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; -use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -35,33 +35,66 @@ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; /** * @see \App\Tests\Serializer\StructuralElementDenormalizerTest */ -class StructuralElementDenormalizer implements DenormalizerInterface +class StructuralElementDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface { + use DenormalizerAwareTrait; + + private const ALREADY_CALLED = 'STRUCTURAL_DENORMALIZER_ALREADY_CALLED'; + private array $object_cache = []; public function __construct( - private readonly EntityManagerInterface $entityManager, - #[Autowire(service: ObjectNormalizer::class)] - private readonly DenormalizerInterface $denormalizer) + private readonly EntityManagerInterface $entityManager) { } - 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)) { + return false; + } + + //If we already handled this object, skip it + if (isset($context[self::ALREADY_CALLED]) + && is_array($context[self::ALREADY_CALLED]) + && in_array($data, $context[self::ALREADY_CALLED], true)) { + return false; + } + return is_array($data) && is_subclass_of($type, AbstractStructuralDBElement::class) //Only denormalize if we are doing a file import operation && in_array('import', $context['groups'] ?? [], true); } - public function denormalize($data, string $type, string $format = null, array $context = []): ?AbstractStructuralDBElement + /** + * @template T of AbstractStructuralDBElement + * @param $data + * @phpstan-param class-string $type + * @param string|null $format + * @param array $context + * @return AbstractStructuralDBElement|null + * @phpstan-return T|null + */ + public function denormalize($data, string $type, ?string $format = null, array $context = []): ?AbstractStructuralDBElement { + //Do not use API Platform's denormalizer + $context[SkippableItemNormalizer::DISABLE_ITEM_NORMALIZER] = true; + + if (!isset($context[self::ALREADY_CALLED])) { + $context[self::ALREADY_CALLED] = []; + } + + $context[self::ALREADY_CALLED][] = $data; + + /** @var AbstractStructuralDBElement $deserialized_entity */ $deserialized_entity = $this->denormalizer->denormalize($data, $type, $format, $context); //Check if we already have the entity in the database (via path) - /** @var StructuralDBElementRepository $repo */ + /** @var StructuralDBElementRepository $repo */ $repo = $this->entityManager->getRepository($type); $path = $deserialized_entity->getFullPath(AbstractStructuralDBElement::PATH_DELIMITER_ARROW); @@ -89,7 +122,7 @@ class StructuralElementDenormalizer implements DenormalizerInterface 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 4277fed4..1d7255b7 100644 --- a/src/Serializer/StructuralElementFromNameDenormalizer.php +++ b/src/Serializer/StructuralElementFromNameDenormalizer.php @@ -25,7 +25,6 @@ namespace App\Serializer; use App\Entity\Base\AbstractStructuralDBElement; use App\Repository\StructuralDBElementRepository; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; /** @@ -37,8 +36,13 @@ class StructuralElementFromNameDenormalizer implements DenormalizerInterface { } - 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)) { + return false; + } + return is_string($data) && is_subclass_of($type, AbstractStructuralDBElement::class); } @@ -47,10 +51,10 @@ class StructuralElementFromNameDenormalizer implements DenormalizerInterface * @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 */ + /** @var StructuralDBElementRepository $repo */ $repo = $this->em->getRepository($type); $path_delimiter = $context['path_delimiter'] ?? '->'; diff --git a/src/Serializer/StructuralElementNormalizer.php b/src/Serializer/StructuralElementNormalizer.php index fc64aec0..bf3e1097 100644 --- a/src/Serializer/StructuralElementNormalizer.php +++ b/src/Serializer/StructuralElementNormalizer.php @@ -23,8 +23,8 @@ declare(strict_types=1); namespace App\Serializer; use App\Entity\Base\AbstractStructuralDBElement; +use App\Serializer\APIPlatform\SkippableItemNormalizer; use Symfony\Component\DependencyInjection\Attribute\Autowire; -use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -33,30 +33,45 @@ 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; } - /** - * @return array - */ - public function normalize($object, string $format = null, array $context = []) + 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 + if (!is_array($data)) { + return $data; + } + //Remove type field for CSV export if ($format === 'csv') { unset($data['type']); @@ -73,7 +88,8 @@ class StructuralElementNormalizer implements NormalizerInterface 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 4429179e..1075141b 100644 --- a/src/Services/Attachments/AttachmentManager.php +++ b/src/Services/Attachments/AttachmentManager.php @@ -44,35 +44,31 @@ class AttachmentManager * * @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 @@ class AttachmentManager } /** - * 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 @@ class AttachmentManager */ 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 @@ class AttachmentManager /** * 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()) { + if (!$this->isInternalFileExisting($attachment)) { return null; } - if (!$this->isFileExisting($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 4e1a7149..1b52c89b 100644 --- a/src/Services/Attachments/AttachmentPathResolver.php +++ b/src/Services/Attachments/AttachmentPathResolver.php @@ -22,7 +22,6 @@ declare(strict_types=1); namespace App\Services\Attachments; -use FontLib\Table\Type\maxp; use const DIRECTORY_SEPARATOR; use Symfony\Component\Filesystem\Filesystem; @@ -116,12 +115,16 @@ class AttachmentPathResolver * 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 @@ -140,12 +143,12 @@ class AttachmentPathResolver } //If we have now have a placeholder left, the string is invalid: - if (preg_match('#%\w+%#', $placeholder_path)) { + if (preg_match('#%\w+%#', (string) $placeholder_path)) { return null; } //Path is invalid if path is directory traversal - if (str_contains($placeholder_path, '..')) { + if (str_contains((string) $placeholder_path, '..')) { return null; } @@ -184,7 +187,7 @@ class AttachmentPathResolver } //If the new string does not begin with a placeholder, it is invalid - if (!preg_match('#^%\w+%#', $real_path)) { + if (!preg_match('#^%\w+%#', (string) $real_path)) { return null; } diff --git a/src/Services/Attachments/AttachmentReverseSearch.php b/src/Services/Attachments/AttachmentReverseSearch.php index 5f4f86de..e05192d0 100644 --- a/src/Services/Attachments/AttachmentReverseSearch.php +++ b/src/Services/Attachments/AttachmentReverseSearch.php @@ -55,7 +55,7 @@ class AttachmentReverseSearch $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 b8b15907..c7e69257 100644 --- a/src/Services/Attachments/AttachmentSubmitHandler.php +++ b/src/Services/Attachments/AttachmentSubmitHandler.php @@ -26,27 +26,30 @@ use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\AttachmentType; use App\Entity\Attachments\AttachmentTypeAttachment; +use App\Entity\Attachments\AttachmentUpload; use App\Entity\Attachments\CategoryAttachment; use App\Entity\Attachments\CurrencyAttachment; use App\Entity\Attachments\LabelAttachment; +use App\Entity\Attachments\PartCustomStateAttachment; use App\Entity\Attachments\ProjectAttachment; use App\Entity\Attachments\FootprintAttachment; use App\Entity\Attachments\GroupAttachment; use App\Entity\Attachments\ManufacturerAttachment; use App\Entity\Attachments\MeasurementUnitAttachment; use App\Entity\Attachments\PartAttachment; -use App\Entity\Attachments\StorelocationAttachment; +use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Attachments\SupplierAttachment; use App\Entity\Attachments\UserAttachment; use App\Exceptions\AttachmentDownloadException; +use App\Settings\SystemSettings\AttachmentsSettings; +use Hshn\Base64EncodedFile\HttpFoundation\File\Base64EncodedFile; +use Hshn\Base64EncodedFile\HttpFoundation\File\UploadedBase64EncodedFile; use const DIRECTORY_SEPARATOR; -use function get_class; use InvalidArgumentException; use RuntimeException; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\Mime\MimeTypesInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -55,6 +58,9 @@ use Symfony\Contracts\HttpClient\HttpClientInterface; */ class AttachmentSubmitHandler { + /** + * @var array The mapping used to determine which folder will be used for an attachment type + */ protected array $folder_mapping; private ?int $max_upload_size_bytes = null; @@ -63,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', @@ -81,7 +90,7 @@ class AttachmentSubmitHandler GroupAttachment::class => 'group', ManufacturerAttachment::class => 'manufacturer', MeasurementUnitAttachment::class => 'measurement_unit', - StorelocationAttachment::class => 'storelocation', + StorageLocationAttachment::class => 'storelocation', SupplierAttachment::class => 'supplier', UserAttachment::class => 'user', LabelAttachment::class => 'label_profile', @@ -143,19 +152,36 @@ class AttachmentSubmitHandler { $base_path = $secure_upload ? $this->pathResolver->getSecurePath() : $this->pathResolver->getMediaPath(); - //Ensure the given attachment class is known to mapping - if (!isset($this->folder_mapping[$attachment::class])) { - throw new InvalidArgumentException('The given attachment class is not known! The passed class was: '.$attachment::class); - } //Ensure the attachment has an assigned element if (!$attachment->getElement() instanceof AttachmentContainingDBElement) { throw new InvalidArgumentException('The given attachment is not assigned to an element! An element is needed to generate a path!'); } + //Determine the folder prefix for the given attachment class: + $prefix = null; + //Check if we can use the class name dire + if (isset($this->folder_mapping[$attachment::class])) { + $prefix = $this->folder_mapping[$attachment::class]; + } else { + //If not, check for instance of: + foreach ($this->folder_mapping as $class => $folder) { + /** @var string $class */ + if ($attachment instanceof $class) { + $prefix = $folder; + break; + } + } + } + + //Ensure the given attachment class is known to mapping + if (!$prefix) { + throw new InvalidArgumentException('The given attachment class is not known! The passed class was: '.$attachment::class); + } + //Build path return $base_path.DIRECTORY_SEPARATOR //Base path - .$this->folder_mapping[$attachment::class].DIRECTORY_SEPARATOR.$attachment->getElement()->getID(); + .$prefix.DIRECTORY_SEPARATOR.$attachment->getElement()->getID(); } /** @@ -163,38 +189,62 @@ class AttachmentSubmitHandler * This function will move the uploaded file or download the URL file to server, if needed. * * @param Attachment $attachment the attachment that should be used for handling - * @param UploadedFile|null $file If given, that file will be moved to the right location - * @param array $options The options to use with the upload. Here you can specify that a URL should be downloaded, - * or an file should be moved to a secure location. + * @param AttachmentUpload|null $upload The upload options DTO. If it is null, it will be tried to get from the attachment option * * @return Attachment The attachment with the new filename (same instance as passed $attachment) */ - public function handleFormSubmit(Attachment $attachment, ?UploadedFile $file, array $options = []): Attachment + public function handleUpload(Attachment $attachment, ?AttachmentUpload $upload): Attachment { - $resolver = new OptionsResolver(); - $this->configureOptions($resolver); - $options = $resolver->resolve($options); + if ($upload === null) { + $upload = $attachment->getUpload(); + if ($upload === null) { + throw new InvalidArgumentException('No upload options given and no upload options set in attachment!'); + } + } + + $file = $upload->file; + + //If no file was uploaded, but we have base64 encoded data, create a file from it + if (!$file && $upload->data !== null) { + $file = new UploadedBase64EncodedFile(new Base64EncodedFile($upload->data), $upload->filename ?? 'base64'); + } + + //By default we assume a public upload + $secure_attachment = $upload->private ?? false; //When a file is given then upload it, otherwise check if we need to download the URL if ($file instanceof UploadedFile) { - $this->upload($attachment, $file, $options); - } elseif ($options['download_url'] && $attachment->isExternal()) { - $this->downloadURL($attachment, $options); + + $this->upload($attachment, $file, $secure_attachment); + } elseif ($upload->downloadUrl && $attachment->hasExternal()) { + $this->downloadURL($attachment, $secure_attachment); } //Move the attachment files to secure location (and back) if needed - $this->moveFile($attachment, $options['secure_attachment']); + $this->moveFile($attachment, $secure_attachment); + + //Sanitize the SVG if needed + $this->sanitizeSVGAttachment($attachment); //Rename blacklisted (unsecure) files to a better extension $this->renameBlacklistedExtensions($attachment); - //Check if we should assign this attachment to master picture - //this is only possible if the attachment is new (not yet persisted to DB) - if ($options['become_preview_if_empty'] && null === $attachment->getID() && $attachment->isPicture()) { - $element = $attachment->getElement(); - if ($element instanceof AttachmentContainingDBElement && !$element->getMasterPictureAttachment() instanceof Attachment) { + //Set / Unset the master picture attachment / preview image + $element = $attachment->getElement(); + if ($element instanceof AttachmentContainingDBElement) { + //Make this attachment the master picture if needed and this was requested + if ($upload->becomePreviewIfEmpty + && $element->getMasterPictureAttachment() === null //Element must not have an preview image yet + && null === $attachment->getID() //Attachment must be null + && $attachment->isPicture() //Attachment must be a picture + ) { $element->setMasterPictureAttachment($attachment); } + + //If this attachment is the master picture, but is not a picture anymore, dont use it as master picture anymore + if ($element->getMasterPictureAttachment() === $attachment && !$attachment->isPicture()) { + $element->setMasterPictureAttachment(null); + } } return $attachment; @@ -206,12 +256,12 @@ class AttachmentSubmitHandler 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; } @@ -222,43 +272,32 @@ class AttachmentSubmitHandler //Check if the extension is blacklisted and replace the file extension with txt if needed if(in_array($ext, self::BLACKLISTED_EXTENSIONS, true)) { $new_path = $this->generateAttachmentPath($attachment, $attachment->isSecure()) - .DIRECTORY_SEPARATOR.$this->generateAttachmentFilename($attachment, 'txt'); + .DIRECTORY_SEPARATOR.$this->generateAttachmentFilename($attachment, 'txt'); //Move file to new directory $fs = new Filesystem(); $fs->rename($old_path, $new_path); //Update the attachment - $attachment->setPath($this->pathResolver->realPathToPlaceholder($new_path)); + $attachment->setInternalPath($this->pathResolver->realPathToPlaceholder($new_path)); } return $attachment; } - protected function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - //If no preview image was set yet, the new uploaded file will become the preview image - 'become_preview_if_empty' => true, - //When a URL is given download the URL - 'download_url' => false, - 'secure_attachment' => false, - ]); - } - /** - * 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; } @@ -268,12 +307,12 @@ class AttachmentSubmitHandler } //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; } - $filename = basename($old_path); + $filename = basename((string) $old_path); //If the basename is not one of the new unique on, we have to save the old filename if (!preg_match('#\w+-\w{13}\.#', $filename)) { //Save filename to attachment field @@ -292,7 +331,7 @@ class AttachmentSubmitHandler //Save info to attachment entity $new_path = $this->pathResolver->realPathToPlaceholder($new_path); - $attachment->setPath($new_path); + $attachment->setInternalPath($new_path); return $attachment; } @@ -300,27 +339,46 @@ class AttachmentSubmitHandler /** * Download the URL set in the attachment and save it on the server. * - * @param array $options The options from the handleFormSubmit function + * @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, array $options): Attachment + 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, $options['secure_attachment']); + $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()); @@ -343,13 +401,15 @@ class AttachmentSubmitHandler //If a content disposition header was set try to extract the filename out of it if (isset($headers['content-disposition'])) { $tmp = []; - preg_match('/[^;\\n=]*=([\'\"])*(.*)(?(1)\1|)/', $headers['content-disposition'][0], $tmp); - $filename = $tmp[2]; + //Only use the filename if the regex matches properly + if (preg_match('/[^;\\n=]*=([\'\"])*(.*)(?(1)\1|)/', $headers['content-disposition'][0], $tmp)) { + $filename = $tmp[2]; + } } //If we don't know filename yet, try to determine it out of url if ('' === $filename) { - $filename = basename(parse_url($url, PHP_URL_PATH)); + $filename = basename(parse_url((string) $url, PHP_URL_PATH)); } //Set original file @@ -357,7 +417,7 @@ class AttachmentSubmitHandler //Check if we have an extension given $pathinfo = pathinfo($filename); - if ($pathinfo['extension'] !== '') { + if (isset($pathinfo['extension']) && $pathinfo['extension'] !== '') { $new_ext = $pathinfo['extension']; } else { //Otherwise we have to guess the extension for the new file, based on its content $new_ext = $this->mimeTypes->getExtensions($this->mimeTypes->guessMimeType($tmp_path))[0] ?? 'tmp'; @@ -370,7 +430,7 @@ class AttachmentSubmitHandler //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!'); } @@ -383,22 +443,24 @@ class AttachmentSubmitHandler * * @param Attachment $attachment The attachment in which the file should be saved * @param UploadedFile $file The file which was uploaded - * @param array $options The options from the handleFormSubmit function + * @param bool $secureAttachment True if the file should be moved to the secure attachment storage * * @return Attachment The attachment with the new filepath */ - protected function upload(Attachment $attachment, UploadedFile $file, array $options): Attachment + protected function upload(Attachment $attachment, UploadedFile $file, bool $secureAttachment): Attachment { - //Move our temporay attachment to its final location + //Move our temporary attachment to its final location $file_path = $file->move( - $this->generateAttachmentPath($attachment, $options['secure_attachment']), + $this->generateAttachmentPath($attachment, $secureAttachment), $this->generateAttachmentFilename($attachment, $file->getClientOriginalExtension()) )->getRealPath(); //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()); @@ -419,7 +481,7 @@ class AttachmentSubmitHandler 'g' => 1000 * 1000 * 1000, 'gi' => 1 << 30, ]; - if (ctype_digit((string) $maxSize)) { + if (ctype_digit($maxSize)) { return (int) $maxSize; } @@ -443,9 +505,37 @@ class AttachmentSubmitHandler $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 afbfade3..e505408f 100644 --- a/src/Services/Attachments/AttachmentURLGenerator.php +++ b/src/Services/Attachments/AttachmentURLGenerator.php @@ -92,9 +92,9 @@ class AttachmentURLGenerator * 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 @@ class AttachmentURLGenerator /** * 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; } @@ -135,7 +139,10 @@ class AttachmentURLGenerator } //GD can not work with SVG, so serve it directly... - if ('svg' === $attachment->getExtension()) { + //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($attachment->getInternalPath(), PHP_URL_PATH) ?? '', PATHINFO_EXTENSION); + if ('svg' === $extension) { return $this->assets->getUrl($asset_path); } @@ -154,7 +161,7 @@ class AttachmentURLGenerator /** * 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/FileTypeFilterTools.php b/src/Services/Attachments/FileTypeFilterTools.php index 3380adb7..d689fda3 100644 --- a/src/Services/Attachments/FileTypeFilterTools.php +++ b/src/Services/Attachments/FileTypeFilterTools.php @@ -120,6 +120,8 @@ class FileTypeFilterTools $element = '.'.$element; } } + //Prevent weird side effects + unset($element); $elements = array_unique($elements); diff --git a/src/Services/Attachments/PartPreviewGenerator.php b/src/Services/Attachments/PartPreviewGenerator.php index 8fe1c72c..9aedba74 100644 --- a/src/Services/Attachments/PartPreviewGenerator.php +++ b/src/Services/Attachments/PartPreviewGenerator.php @@ -23,9 +23,10 @@ declare(strict_types=1); namespace App\Services\Attachments; use App\Entity\Parts\Footprint; +use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Manufacturer; use App\Entity\Attachments\Attachment; @@ -88,7 +89,7 @@ class PartPreviewGenerator } foreach ($part->getPartLots() as $lot) { - if ($lot->getStorageLocation() instanceof Storelocation) { + if ($lot->getStorageLocation() instanceof StorageLocation) { $attachment = $lot->getStorageLocation()->getMasterPictureAttachment(); if ($this->isAttachmentValidPicture($attachment)) { $list[] = $attachment; diff --git a/src/Services/Attachments/SVGSanitizer.php b/src/Services/Attachments/SVGSanitizer.php new file mode 100644 index 00000000..9ac5956b --- /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/Cache/ElementCacheTagGenerator.php b/src/Services/Cache/ElementCacheTagGenerator.php new file mode 100644 index 00000000..88fca09f --- /dev/null +++ b/src/Services/Cache/ElementCacheTagGenerator.php @@ -0,0 +1,69 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\Cache; + +use Doctrine\Persistence\Proxy; + +/** + * The purpose of this class is to generate cache tags for elements. + * E.g. to easily invalidate all caches for a given element type. + */ +class ElementCacheTagGenerator +{ + private array $cache = []; + + public function __construct() + { + } + + /** + * Returns a cache tag for the given element type, which can be used to invalidate all caches for this element type. + * @param string|object $element + * @return string + */ + public function getElementTypeCacheTag(string|object $element): string + { + //Ensure that the given element is a class name + if (is_object($element)) { + $element = $element::class; + } elseif (!class_exists($element)) { + //And that the class exists + throw new \InvalidArgumentException("The given class '$element' does not exist!"); + } + + //Check if the tag is already cached + if (isset($this->cache[$element])) { + return $this->cache[$element]; + } + + //If the element is a proxy, then get the real class name of the underlying object + if (is_a($element, Proxy::class, true) || str_starts_with($element, 'Proxies\\')) { + $element = get_parent_class($element); + } + + //Replace all backslashes with underscores to prevent problems with the cache and save the result + $this->cache[$element] = str_replace('\\', '_', $element); + return $this->cache[$element]; + } +} \ No newline at end of file diff --git a/src/Services/UserSystem/UserCacheKeyGenerator.php b/src/Services/Cache/UserCacheKeyGenerator.php similarity index 93% rename from src/Services/UserSystem/UserCacheKeyGenerator.php rename to src/Services/Cache/UserCacheKeyGenerator.php index f8aec6a1..ac5487a5 100644 --- a/src/Services/UserSystem/UserCacheKeyGenerator.php +++ b/src/Services/Cache/UserCacheKeyGenerator.php @@ -20,12 +20,12 @@ declare(strict_types=1); -namespace App\Services\UserSystem; +namespace App\Services\Cache; -use Symfony\Bundle\SecurityBundle\Security; -use Symfony\Component\HttpFoundation\Request; use App\Entity\UserSystem\User; use Locale; +use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; /** @@ -60,7 +60,7 @@ class UserCacheKeyGenerator return 'user$_'.User::ID_ANONYMOUS; } - //In the most cases we can just use the username (its unique) - return 'user_'.$user->getUsername().'_'.$locale; + //Use the unique user id and the locale to generate the key + return 'user_'.$user->getID().'_'.$locale; } } diff --git a/src/Services/Misc/DBInfoHelper.php b/src/Services/Doctrine/DBInfoHelper.php similarity index 66% rename from src/Services/Misc/DBInfoHelper.php rename to src/Services/Doctrine/DBInfoHelper.php index 29440d26..160e2d89 100644 --- a/src/Services/Misc/DBInfoHelper.php +++ b/src/Services/Doctrine/DBInfoHelper.php @@ -1,4 +1,22 @@ . + */ declare(strict_types=1); @@ -20,12 +38,13 @@ declare(strict_types=1); * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -namespace App\Services\Misc; +namespace App\Services\Doctrine; -use Doctrine\DBAL\Exception; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\ORM\EntityManagerInterface; /** @@ -50,10 +69,14 @@ class DBInfoHelper return 'mysql'; } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { return 'sqlite'; } + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + return 'postgresql'; + } + return null; } @@ -67,10 +90,14 @@ class DBInfoHelper return $this->connection->fetchOne('SELECT VERSION()'); } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { return $this->connection->fetchOne('SELECT sqlite_version()'); } + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + return $this->connection->fetchOne('SELECT version()'); + } + return null; } @@ -89,7 +116,7 @@ class DBInfoHelper } } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { try { return (int) $this->connection->fetchOne('SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();'); } catch (Exception) { @@ -97,6 +124,14 @@ class DBInfoHelper } } + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + try { + return (int) $this->connection->fetchOne('SELECT pg_database_size(current_database())'); + } catch (Exception) { + return null; + } + } + return null; } @@ -105,7 +140,7 @@ class DBInfoHelper */ public function getDatabaseName(): ?string { - return $this->connection->getDatabase() ?? null; + return $this->connection->getDatabase(); } /** @@ -121,9 +156,17 @@ class DBInfoHelper } } - if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + if ($this->connection->getDatabasePlatform() instanceof SQLitePlatform) { return 'sqlite'; } + + if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + try { + return $this->connection->fetchOne('SELECT current_user'); + } catch (Exception) { + return null; + } + } return null; } diff --git a/src/Services/Doctrine/NatsortDebugHelper.php b/src/Services/Doctrine/NatsortDebugHelper.php new file mode 100644 index 00000000..fe5b77aa --- /dev/null +++ b/src/Services/Doctrine/NatsortDebugHelper.php @@ -0,0 +1,86 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\Doctrine; + +use App\Doctrine\Functions\Natsort; +use App\Entity\Parts\Part; +use Doctrine\ORM\EntityManagerInterface; + +/** + * This service allows to debug the natsort function by showing various information about the current state of + * the natsort function. + */ +class NatsortDebugHelper +{ + public function __construct(private readonly EntityManagerInterface $entityManager) + { + // This is a dummy constructor + } + + /** + * Check if the slow natural sort is allowed on the Natsort function. + * If it is not, then the request handler might need to be adjusted. + * @return bool + */ + public function isSlowNaturalSortAllowed(): bool + { + return Natsort::isSlowNaturalSortAllowed(); + } + + public function getNaturalSortMethod(): string + { + //Construct a dummy query which uses the Natsort function + $query = $this->entityManager->createQuery('SELECT natsort(1) FROM ' . Part::class . ' p'); + $sql = $query->getSQL(); + //Remove the leading SELECT and the trailing semicolon + $sql = substr($sql, 7, -1); + + //Remove AS and everything afterwards + $sql = preg_replace('/\s+AS\s+.*/', '', $sql); + + //If just 1 is returned, then we use normal (non-natural sorting) + if ($sql === '1') { + return 'Disabled'; + } + + if (str_contains( $sql, 'COLLATE numeric')) { + return 'Native (PostgreSQL)'; + } + + if (str_contains($sql, 'NATURAL_SORT_KEY')) { + return 'Native (MariaDB)'; + } + + if (str_contains($sql, 'COLLATE NATURAL_CMP')) { + return 'Emulation via PHP (SQLite)'; + } + + if (str_contains($sql, 'NatSortKey')) { + return 'Emulation via custom function (MySQL)'; + } + + + return 'Unknown ('. $sql . ')'; + } +} \ No newline at end of file diff --git a/src/Services/EDA/KiCadHelper.php b/src/Services/EDA/KiCadHelper.php new file mode 100644 index 00000000..4b7c5e5a --- /dev/null +++ b/src/Services/EDA/KiCadHelper.php @@ -0,0 +1,398 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\EDA; + +use App\Entity\Parts\Category; +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; +use Symfony\Contracts\Cache\ItemInterface; +use Symfony\Contracts\Cache\TagAwareCacheInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +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, + KiCadEDASettings $kiCadEDASettings, + ) { + $this->category_depth = $kiCadEDASettings->categoryDepth; + } + + /** + * Returns an array of objects containing all categories in the database in the format required by KiCAD. + * The categories are flattened and sorted by their full path. + * Categories, which contain no parts, are filtered out. + * The result is cached for performance and invalidated on category changes. + * @return array + */ + public function getCategories(): array + { + return $this->kicadCache->get('kicad_categories_' . $this->category_depth, function (ItemInterface $item) { + //Invalidate the cache on category changes + $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 [ + [ + 'id' => '0', + 'name' => 'Part-DB', + ] + ]; + } + + //Otherwise just get the categories and filter them + + $categories = $this->nodesListBuilder->typeToNodesList(Category::class); + $repo = $this->em->getRepository(Category::class); + $result = []; + foreach ($categories as $category) { + //Skip invisible categories + if ($category->getEdaInfo()->getVisibility() === false) { + continue; + } + + //Skip categories with a depth greater than the configured one + if ($category->getLevel() > $this->category_depth) { + continue; + } + + //Ensure that the category contains parts + //For the last level, we need to use a recursive query, otherwise we can use a simple query + /** @var Category $category */ + $parts_count = $category->getLevel() >= $this->category_depth ? $repo->getPartsCountRecursive($category) : $repo->getPartsCount($category); + + if ($parts_count < 1) { + continue; + } + + //Check if the category should be visible + if (!$this->shouldCategoryBeVisible($category)) { + continue; + } + + //Format the category for KiCAD + $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), + ]; + } + + return $result; + }); + } + + /** + * Returns an array of objects containing all parts for the given category in the format required by KiCAD. + * The result is cached for performance and invalidated on category or part changes. + * @param Category|null $category + * @return array + */ + public function getCategoryParts(?Category $category): array + { + return $this->kicadCache->get('kicad_category_parts_'.($category?->getID() ?? 0) . '_' . $this->category_depth, + function (ItemInterface $item) use ($category) { + $item->tag([ + $this->tagGenerator->getElementTypeCacheTag(Category::class), + $this->tagGenerator->getElementTypeCacheTag(Part::class), + //Visibility can change based on the footprint + $this->tagGenerator->getElementTypeCacheTag(Footprint::class) + ]); + + if ($this->category_depth >= 0) { + //Ensure that the category is set + if ($category === null) { + throw new NotFoundHttpException('Category must be set, if category_depth is greater than 1!'); + } + + $category_repo = $this->em->getRepository(Category::class); + if ($category->getLevel() >= $this->category_depth) { + //Get all parts for the category and its children + $parts = $category_repo->getPartsRecursive($category); + } else { + //Get only direct parts for the category (without children), as the category is not collapsed + $parts = $category_repo->getParts($category); + } + } else { + //Get all parts + $parts = $this->em->getRepository(Part::class)->findAll(); + } + + $result = []; + foreach ($parts as $part) { + //If the part is invisible, then skip it + if (!$this->shouldPartBeVisible($part)) { + continue; + } + + $result[] = [ + 'id' => (string)$part->getId(), + 'name' => $part->getName(), + 'description' => $part->getDescription(), + ]; + } + + return $result; + }); + } + + public function getKiCADPart(Part $part): array + { + $result = [ + 'id' => (string)$part->getId(), + 'name' => $part->getName(), + "symbolIdStr" => $part->getEdaInfo()->getKicadSymbol() ?? $part->getCategory()?->getEdaInfo()->getKicadSymbol() ?? "", + "exclude_from_bom" => $this->boolToKicadBool($part->getEdaInfo()->getExcludeFromBom() ?? $part->getCategory()?->getEdaInfo()->getExcludeFromBom() ?? false), + "exclude_from_board" => $this->boolToKicadBool($part->getEdaInfo()->getExcludeFromBoard() ?? $part->getCategory()?->getEdaInfo()->getExcludeFromBoard() ?? false), + "exclude_from_sim" => $this->boolToKicadBool($part->getEdaInfo()->getExcludeFromSim() ?? $part->getCategory()?->getEdaInfo()->getExcludeFromSim() ?? true), + "fields" => [] + ]; + + $result["fields"]["footprint"] = $this->createField($part->getEdaInfo()->getKicadFootprint() ?? $part->getFootprint()?->getEdaInfo()->getKicadFootprint() ?? ""); + $result["fields"]["reference"] = $this->createField($part->getEdaInfo()->getReferencePrefix() ?? $part->getCategory()?->getEdaInfo()->getReferencePrefix() ?? 'U', true); + $result["fields"]["value"] = $this->createField($part->getEdaInfo()->getValue() ?? $part->getName(), true); + $result["fields"]["keywords"] = $this->createField($part->getTags()); + + //Use the part info page as datasheet link. It must be an absolute URL. + $result["fields"]["datasheet"] = $this->createField( + $this->urlGenerator->generate( + 'part_info', + ['id' => $part->getId()], + UrlGeneratorInterface::ABSOLUTE_URL) + ); + + //Add basic fields + $result["fields"]["description"] = $this->createField($part->getDescription()); + if ($part->getCategory() !== null) { + $result["fields"]["Category"] = $this->createField($part->getCategory()->getFullPath('/')); + } + if ($part->getManufacturer() !== null) { + $result["fields"]["Manufacturer"] = $this->createField($part->getManufacturer()->getName()); + } + if ($part->getManufacturerProductNumber() !== "") { + $result['fields']["MPN"] = $this->createField($part->getManufacturerProductNumber()); + } + if ($part->getManufacturingStatus() !== null) { + $result["fields"]["Manufacturing Status"] = $this->createField( + //Always use the english translation + $this->translator->trans($part->getManufacturingStatus()->toTranslationKey(), locale: 'en') + ); + } + if ($part->getFootprint() !== null) { + $result["fields"]["Part-DB Footprint"] = $this->createField($part->getFootprint()->getName()); + } + if ($part->getPartUnit() !== null) { + $unit = $part->getPartUnit()->getName(); + if ($part->getPartUnit()->getUnit() !== "") { + $unit .= ' ('.$part->getPartUnit()->getUnit().')'; + } + $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'); + } + $result["fields"]["Part-DB ID"] = $this->createField($part->getId()); + if ($part->getIpn() !== null && $part->getIpn() !== '' && $part->getIpn() !== '0') { + $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; + } + + /** + * Determine if the given part should be visible for the EDA. + * @param Category $category + * @return bool + */ + private function shouldCategoryBeVisible(Category $category): bool + { + $eda_info = $category->getEdaInfo(); + + //If the category visibility is explicitly set, then use it + if ($eda_info->getVisibility() !== null) { + return $eda_info->getVisibility(); + } + + //try to check if the fields were set + if ($eda_info->getKicadSymbol() !== null + || $eda_info->getReferencePrefix() !== null) { + return true; + } + + //Check if there is any part in this category, which should be visible + $category_repo = $this->em->getRepository(Category::class); + if ($category->getLevel() >= $this->category_depth) { + //Get all parts for the category and its children + $parts = $category_repo->getPartsRecursive($category); + } else { + //Get only direct parts for the category (without children), as the category is not collapsed + $parts = $category_repo->getParts($category); + } + + foreach ($parts as $part) { + if ($this->shouldPartBeVisible($part)) { + return true; + } + } + + //Otherwise the category should be not visible + return false; + } + + /** + * Determine if the given part should be visible for the EDA. + * @param Part $part + * @return bool + */ + private function shouldPartBeVisible(Part $part): bool + { + $eda_info = $part->getEdaInfo(); + $category = $part->getCategory(); + + //If the user set a visibility, then use it + if ($eda_info->getVisibility() !== null) { + return $part->getEdaInfo()->getVisibility(); + } + + //If the part has a category, then use the category visibility if possible + if ($category && $category->getEdaInfo()->getVisibility() !== null) { + return $category->getEdaInfo()->getVisibility(); + } + + //If both are null, then we try to determine the visibility based on if fields are set + if ($eda_info->getKicadSymbol() !== null + || $eda_info->getKicadFootprint() !== null + || $eda_info->getReferencePrefix() !== null + || $eda_info->getValue() !== null) { + return true; + } + + //Check also if the fields are set for the category (if it exists) + if ($category && ( + $category->getEdaInfo()->getKicadSymbol() !== null + || $category->getEdaInfo()->getReferencePrefix() !== null + )) { + return true; + } + //And on the footprint + //Otherwise the part should be not visible + return $part->getFootprint() && $part->getFootprint()->getEdaInfo()->getKicadFootprint() !== null; + } + + /** + * Converts a boolean value to the format required by KiCAD. + * @param bool $value + * @return string + */ + private function boolToKicadBool(bool $value): string + { + return $value ? 'True' : 'False'; + } + + /** + * Creates a field array for KiCAD + * @param string|int|float $value + * @param bool $visible + * @return array + */ + private function createField(string|int|float $value, bool $visible = false): array + { + return [ + 'value' => (string)$value, + 'visible' => $this->boolToKicadBool($visible), + ]; + } +} \ No newline at end of file diff --git a/src/Services/ElementTypeNameGenerator.php b/src/Services/ElementTypeNameGenerator.php index f32677eb..19bb19f5 100644 --- a/src/Services/ElementTypeNameGenerator.php +++ b/src/Services/ElementTypeNameGenerator.php @@ -22,65 +22,33 @@ declare(strict_types=1); namespace App\Services; -use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentType; +use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Base\AbstractDBElement; -use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Contracts\NamedElementInterface; -use App\Entity\ProjectSystem\Project; -use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parameters\AbstractParameter; -use App\Entity\Parts\Category; -use App\Entity\Parts\Footprint; -use App\Entity\Parts\Manufacturer; -use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; -use App\Entity\Parts\Supplier; -use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Pricedetail; +use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; -use App\Entity\UserSystem\Group; -use App\Entity\UserSystem\User; use App\Exceptions\EntityNotSupportedException; -use Doctrine\ORM\Mapping\Entity; -use function get_class; +use App\Settings\SynonymSettings; use Symfony\Contracts\Translation\TranslatorInterface; /** * @see \App\Tests\Services\ElementTypeNameGeneratorTest */ -class ElementTypeNameGenerator +final readonly class ElementTypeNameGenerator { - protected array $mapping; - public function __construct(protected TranslatorInterface $translator, private readonly EntityURLGenerator $entityURLGenerator) + public function __construct( + private TranslatorInterface $translator, + private EntityURLGenerator $entityURLGenerator, + private SynonymSettings $synonymsSettings, + ) { - //Child classes has to become before parent classes - $this->mapping = [ - Attachment::class => $this->translator->trans('attachment.label'), - Category::class => $this->translator->trans('category.label'), - AttachmentType::class => $this->translator->trans('attachment_type.label'), - Project::class => $this->translator->trans('project.label'), - ProjectBOMEntry::class => $this->translator->trans('project_bom_entry.label'), - Footprint::class => $this->translator->trans('footprint.label'), - Manufacturer::class => $this->translator->trans('manufacturer.label'), - MeasurementUnit::class => $this->translator->trans('measurement_unit.label'), - Part::class => $this->translator->trans('part.label'), - PartLot::class => $this->translator->trans('part_lot.label'), - Storelocation::class => $this->translator->trans('storelocation.label'), - Supplier::class => $this->translator->trans('supplier.label'), - Currency::class => $this->translator->trans('currency.label'), - Orderdetail::class => $this->translator->trans('orderdetail.label'), - Pricedetail::class => $this->translator->trans('pricedetail.label'), - Group::class => $this->translator->trans('group.label'), - User::class => $this->translator->trans('user.label'), - AbstractParameter::class => $this->translator->trans('parameter.label'), - LabelProfile::class => $this->translator->trans('label_profile.label'), - ]; } /** @@ -94,27 +62,69 @@ class ElementTypeNameGenerator * @return string the localized label for the entity type * * @throws EntityNotSupportedException when the passed entity is not supported + * @deprecated Use label() instead */ public function getLocalizedTypeLabel(object|string $entity): string { - $class = is_string($entity) ? $entity : $entity::class; - - //Check if we have a direct array entry for our entity class, then we can use it - if (isset($this->mapping[$class])) { - return $this->mapping[$class]; - } - - //Otherwise iterate over array and check for inheritance (needed when the proxy element from doctrine are passed) - foreach ($this->mapping as $class_to_check => $translation) { - if (is_a($entity, $class_to_check, true)) { - return $translation; - } - } - - //When nothing was found throw an exception - throw new EntityNotSupportedException(sprintf('No localized label for the element with type %s was found!', is_object($entity) ? $entity::class : (string) $entity)); + return $this->typeLabel($entity); } + private function resolveSynonymLabel(ElementTypes $type, ?string $locale, bool $plural): ?string + { + $locale ??= $this->translator->getLocale(); + + if ($this->synonymsSettings->isSynonymDefinedForType($type)) { + if ($plural) { + $syn = $this->synonymsSettings->getPluralSynonymForType($type, $locale); + } else { + $syn = $this->synonymsSettings->getSingularSynonymForType($type, $locale); + } + + if ($syn === null) { + //Try to fall back to english + if ($plural) { + $syn = $this->synonymsSettings->getPluralSynonymForType($type, 'en'); + } else { + $syn = $this->synonymsSettings->getSingularSynonymForType($type, 'en'); + } + } + + return $syn; + } + + return null; + } + + /** + * Gets a localized label for the type of the entity. If user defined synonyms are defined, + * these are used instead of the default labels. + * @param object|string $entity + * @param string|null $locale + * @return string + */ + public function typeLabel(object|string $entity, ?string $locale = null): string + { + $type = ElementTypes::fromValue($entity); + + return $this->resolveSynonymLabel($type, $locale, false) + ?? $this->translator->trans($type->getDefaultLabelKey(), locale: $locale); + } + + /** + * Similar to label(), but returns the plural version of the label. + * @param object|string $entity + * @param string|null $locale + * @return string + */ + public function typeLabelPlural(object|string $entity, ?string $locale = null): string + { + $type = ElementTypes::fromValue($entity); + + return $this->resolveSynonymLabel($type, $locale, true) + ?? $this->translator->trans($type->getDefaultPluralLabelKey(), locale: $locale); + } + + /** * Returns a string like in the format ElementType: ElementName. * For example this could be something like: "Part: BC547". @@ -129,17 +139,17 @@ class ElementTypeNameGenerator */ public function getTypeNameCombination(NamedElementInterface $entity, bool $use_html = false): string { - $type = $this->getLocalizedTypeLabel($entity); + $type = $this->typeLabel($entity); if ($use_html) { - return ''.$type.': '.htmlspecialchars((string) $entity->getName()); + return '' . $type . ': ' . htmlspecialchars($entity->getName()); } - return $type.': '.$entity->getName(); + return $type . ': ' . $entity->getName(); } /** - * Returns a HTML formatted label for the given enitity in the format "Type: Name" (on elements with a name) and + * Returns a HTML formatted label for the given entity in the format "Type: Name" (on elements with a name) and * "Type: ID" (on elements without a name). If possible the value is given as a link to the element. * @param AbstractDBElement $entity The entity for which the label should be generated * @param bool $include_associated If set to true, the associated entity (like the part belonging to a part lot) is included in the label to give further information @@ -160,7 +170,7 @@ class ElementTypeNameGenerator } else { //Target does not have a name $tmp = sprintf( '%s: %s', - $this->getLocalizedTypeLabel($entity), + $this->typeLabel($entity), $entity->getID() ); } @@ -204,7 +214,7 @@ class ElementTypeNameGenerator { return sprintf( '%s: %s [%s]', - $this->getLocalizedTypeLabel($class), + $this->typeLabel($class), $id, $this->translator->trans('log.target_deleted') ); diff --git a/src/Services/ElementTypes.php b/src/Services/ElementTypes.php new file mode 100644 index 00000000..6ce8f851 --- /dev/null +++ b/src/Services/ElementTypes.php @@ -0,0 +1,229 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services; + +use App\Entity\Attachments\Attachment; +use App\Entity\Attachments\AttachmentType; +use App\Entity\InfoProviderSystem\BulkInfoProviderImportJob; +use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; +use App\Entity\LabelSystem\LabelProfile; +use App\Entity\Parameters\AbstractParameter; +use App\Entity\Parts\Category; +use App\Entity\Parts\Footprint; +use App\Entity\Parts\Manufacturer; +use App\Entity\Parts\MeasurementUnit; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartAssociation; +use App\Entity\Parts\PartCustomState; +use App\Entity\Parts\PartLot; +use App\Entity\Parts\StorageLocation; +use App\Entity\Parts\Supplier; +use App\Entity\PriceInformations\Currency; +use App\Entity\PriceInformations\Orderdetail; +use App\Entity\PriceInformations\Pricedetail; +use App\Entity\ProjectSystem\Project; +use App\Entity\ProjectSystem\ProjectBOMEntry; +use App\Entity\UserSystem\Group; +use App\Entity\UserSystem\User; +use App\Exceptions\EntityNotSupportedException; +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +enum ElementTypes: string implements TranslatableInterface +{ + case ATTACHMENT = "attachment"; + case CATEGORY = "category"; + case ATTACHMENT_TYPE = "attachment_type"; + case PROJECT = "project"; + case PROJECT_BOM_ENTRY = "project_bom_entry"; + case FOOTPRINT = "footprint"; + case MANUFACTURER = "manufacturer"; + case MEASUREMENT_UNIT = "measurement_unit"; + case PART = "part"; + case PART_LOT = "part_lot"; + case STORAGE_LOCATION = "storage_location"; + case SUPPLIER = "supplier"; + case CURRENCY = "currency"; + case ORDERDETAIL = "orderdetail"; + case PRICEDETAIL = "pricedetail"; + case GROUP = "group"; + case USER = "user"; + case PARAMETER = "parameter"; + case LABEL_PROFILE = "label_profile"; + case PART_ASSOCIATION = "part_association"; + case BULK_INFO_PROVIDER_IMPORT_JOB = "bulk_info_provider_import_job"; + case BULK_INFO_PROVIDER_IMPORT_JOB_PART = "bulk_info_provider_import_job_part"; + case PART_CUSTOM_STATE = "part_custom_state"; + + //Child classes has to become before parent classes + private const CLASS_MAPPING = [ + Attachment::class => self::ATTACHMENT, + Category::class => self::CATEGORY, + AttachmentType::class => self::ATTACHMENT_TYPE, + Project::class => self::PROJECT, + ProjectBOMEntry::class => self::PROJECT_BOM_ENTRY, + Footprint::class => self::FOOTPRINT, + Manufacturer::class => self::MANUFACTURER, + MeasurementUnit::class => self::MEASUREMENT_UNIT, + Part::class => self::PART, + PartLot::class => self::PART_LOT, + StorageLocation::class => self::STORAGE_LOCATION, + Supplier::class => self::SUPPLIER, + Currency::class => self::CURRENCY, + Orderdetail::class => self::ORDERDETAIL, + Pricedetail::class => self::PRICEDETAIL, + Group::class => self::GROUP, + User::class => self::USER, + AbstractParameter::class => self::PARAMETER, + LabelProfile::class => self::LABEL_PROFILE, + PartAssociation::class => self::PART_ASSOCIATION, + BulkInfoProviderImportJob::class => self::BULK_INFO_PROVIDER_IMPORT_JOB, + BulkInfoProviderImportJobPart::class => self::BULK_INFO_PROVIDER_IMPORT_JOB_PART, + PartCustomState::class => self::PART_CUSTOM_STATE, + ]; + + /** + * Gets the default translation key for the label of the element type (singular form). + */ + public function getDefaultLabelKey(): string + { + return match ($this) { + self::ATTACHMENT => 'attachment.label', + self::CATEGORY => 'category.label', + self::ATTACHMENT_TYPE => 'attachment_type.label', + self::PROJECT => 'project.label', + self::PROJECT_BOM_ENTRY => 'project_bom_entry.label', + self::FOOTPRINT => 'footprint.label', + self::MANUFACTURER => 'manufacturer.label', + self::MEASUREMENT_UNIT => 'measurement_unit.label', + self::PART => 'part.label', + self::PART_LOT => 'part_lot.label', + self::STORAGE_LOCATION => 'storelocation.label', + self::SUPPLIER => 'supplier.label', + self::CURRENCY => 'currency.label', + self::ORDERDETAIL => 'orderdetail.label', + self::PRICEDETAIL => 'pricedetail.label', + self::GROUP => 'group.label', + self::USER => 'user.label', + self::PARAMETER => 'parameter.label', + self::LABEL_PROFILE => 'label_profile.label', + self::PART_ASSOCIATION => 'part_association.label', + self::BULK_INFO_PROVIDER_IMPORT_JOB => 'bulk_info_provider_import_job.label', + self::BULK_INFO_PROVIDER_IMPORT_JOB_PART => 'bulk_info_provider_import_job_part.label', + self::PART_CUSTOM_STATE => 'part_custom_state.label', + }; + } + + public function getDefaultPluralLabelKey(): string + { + return match ($this) { + self::ATTACHMENT => 'attachment.labelp', + self::CATEGORY => 'category.labelp', + self::ATTACHMENT_TYPE => 'attachment_type.labelp', + self::PROJECT => 'project.labelp', + self::PROJECT_BOM_ENTRY => 'project_bom_entry.labelp', + self::FOOTPRINT => 'footprint.labelp', + self::MANUFACTURER => 'manufacturer.labelp', + self::MEASUREMENT_UNIT => 'measurement_unit.labelp', + self::PART => 'part.labelp', + self::PART_LOT => 'part_lot.labelp', + self::STORAGE_LOCATION => 'storelocation.labelp', + self::SUPPLIER => 'supplier.labelp', + self::CURRENCY => 'currency.labelp', + self::ORDERDETAIL => 'orderdetail.labelp', + self::PRICEDETAIL => 'pricedetail.labelp', + self::GROUP => 'group.labelp', + self::USER => 'user.labelp', + self::PARAMETER => 'parameter.labelp', + self::LABEL_PROFILE => 'label_profile.labelp', + self::PART_ASSOCIATION => 'part_association.labelp', + self::BULK_INFO_PROVIDER_IMPORT_JOB => 'bulk_info_provider_import_job.labelp', + self::BULK_INFO_PROVIDER_IMPORT_JOB_PART => 'bulk_info_provider_import_job_part.labelp', + self::PART_CUSTOM_STATE => 'part_custom_state.labelp', + }; + } + + /** + * Used to get a user-friendly representation of the object that can be translated. + * For this the singular default label key is used. + * @param TranslatorInterface $translator + * @param string|null $locale + * @return string + */ + public function trans(TranslatorInterface $translator, ?string $locale = null): string + { + return $translator->trans($this->getDefaultLabelKey(), locale: $locale); + } + + /** + * Determines the ElementType from a value, which can either be an enum value, an ElementTypes instance, a class name or an object instance. + * @param string|object $value + * @return self + */ + public static function fromValue(string|object $value): self + { + if ($value instanceof self) { + return $value; + } + if (is_object($value)) { + return self::fromClass($value); + } + + + //Otherwise try to parse it as enum value first + $enumValue = self::tryFrom($value); + + //Otherwise try to get it from class name + return $enumValue ?? self::fromClass($value); + } + + /** + * Determines the ElementType from a class name or object instance. + * @param string|object $class + * @throws EntityNotSupportedException if the class is not supported + * @return self + */ + public static function fromClass(string|object $class): self + { + if (is_object($class)) { + $className = get_class($class); + } else { + $className = $class; + } + + if (array_key_exists($className, self::CLASS_MAPPING)) { + return self::CLASS_MAPPING[$className]; + } + + //Otherwise we need to check for inheritance + foreach (self::CLASS_MAPPING as $entityClass => $elementType) { + if (is_a($className, $entityClass, true)) { + return $elementType; + } + } + + throw new EntityNotSupportedException(sprintf('No localized label for the element with type %s was found!', $className)); + } + +} diff --git a/src/Services/EntityMergers/EntityMerger.php b/src/Services/EntityMergers/EntityMerger.php new file mode 100644 index 00000000..f8cf8a11 --- /dev/null +++ b/src/Services/EntityMergers/EntityMerger.php @@ -0,0 +1,76 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\EntityMergers; + +use App\Services\EntityMergers\Mergers\EntityMergerInterface; +use Symfony\Component\DependencyInjection\Attribute\AutowireIterator; + +/** + * This service is used to merge two entities together. + * It automatically finds the correct merger (implementing EntityMergerInterface) for the two entities if one exists. + */ +class EntityMerger +{ + public function __construct(#[AutowireIterator('app.entity_merger')] protected iterable $mergers) + { + } + + /** + * This function finds the first merger that supports merging the other entity into the target entity. + * @param object $target + * @param object $other + * @param array $context + * @return EntityMergerInterface|null + */ + public function findMergerForObject(object $target, object $other, array $context = []): ?EntityMergerInterface + { + foreach ($this->mergers as $merger) { + if ($merger->supports($target, $other, $context)) { + return $merger; + } + } + return null; + } + + /** + * This function merges the other entity into the target entity. If no merger is found an exception is thrown. + * The target entity will be modified and returned. + * @param object $target + * @param object $other + * @param array $context + * @template T of object + * @phpstan-param T $target + * @phpstan-param T $other + * @phpstan-return T + * @return object + */ + public function merge(object $target, object $other, array $context = []): object + { + $merger = $this->findMergerForObject($target, $other, $context); + if ($merger === null) { + throw new \RuntimeException('No merger found for merging '.$other::class.' into '.$target::class); + } + return $merger->merge($target, $other, $context); + } +} diff --git a/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php b/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php new file mode 100644 index 00000000..64c952a9 --- /dev/null +++ b/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php @@ -0,0 +1,358 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\EntityMergers\Mergers; + +use App\Entity\Attachments\Attachment; +use App\Entity\Attachments\AttachmentContainingDBElement; +use App\Entity\Base\AbstractNamedDBElement; +use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\Parameters\AbstractParameter; +use App\Entity\Parts\Part; +use Doctrine\Common\Collections\Collection; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Contracts\Service\Attribute\Required; + +use function Symfony\Component\String\u; + +/** + * This trait provides helper methods for entity mergers. + * By default, it uses the value from the target entity, unless it not fullfills a condition. + */ +trait EntityMergerHelperTrait +{ + protected PropertyAccessorInterface $property_accessor; + + #[Required] + public function setPropertyAccessor(PropertyAccessorInterface $property_accessor): void + { + $this->property_accessor = $property_accessor; + } + + /** + * Choice the value to use from the target or the other entity by using a callback function. + * + * @param callable $callback The callback to use. The signature is: function($target_value, $other_value, $target, $other, $field). The callback should return the value to use. + * @param object $target The target entity + * @param object $other The other entity + * @param string $field The field to use + * @return object The target entity with the value set + */ + protected function useCallback(callable $callback, object $target, object $other, string $field): object + { + //Get the values from the entities + $target_value = $this->property_accessor->getValue($target, $field); + $other_value = $this->property_accessor->getValue($other, $field); + + //Call the callback, with the signature: function($target_value, $other_value, $target, $other, $field) + //The callback should return the value to use + $value = $callback($target_value, $other_value, $target, $other, $field); + + //Set the value + $this->property_accessor->setValue($target, $field, $value); + + return $target; + } + + /** + * Use the value from the other entity, if the value from the target entity is null. + * + * @param object $target The target entity + * @param object $other The other entity + * @param string $field The field to use + * @return object The target entity with the value set + */ + protected function useOtherValueIfNotNull(object $target, object $other, string $field): object + { + return $this->useCallback( + fn($target_value, $other_value) => $target_value ?? $other_value, + $target, + $other, + $field + ); + + } + + /** + * Use the value from the other entity, if the value from the target entity is empty. + * + * @param object $target The target entity + * @param object $other The other entity + * @param string $field The field to use + * @return object The target entity with the value set + */ + protected function useOtherValueIfNotEmtpy(object $target, object $other, string $field): object + { + return $this->useCallback( + fn($target_value, $other_value) => empty($target_value) ? $other_value : $target_value, + $target, + $other, + $field + ); + } + + /** + * Use the larger value from the target and the other entity for the given field. + * + * @param object $target + * @param object $other + * @param string $field + * @return object + */ + protected function useLargerValue(object $target, object $other, string $field): object + { + return $this->useCallback( + fn($target_value, $other_value) => max($target_value, $other_value), + $target, + $other, + $field + ); + } + + /** + * Use the smaller value from the target and the other entity for the given field. + * + * @param object $target + * @param object $other + * @param string $field + * @return object + */ + protected function useSmallerValue(object $target, object $other, string $field): object + { + return $this->useCallback( + fn($target_value, $other_value) => min($target_value, $other_value), + $target, + $other, + $field + ); + } + + /** + * Perform an OR operation on the boolean values from the target and the other entity for the given field. + * This effectively means that the value is true, if it is true in at least one of the entities. + * @param object $target + * @param object $other + * @param string $field + * @return object + */ + protected function useTrueValue(object $target, object $other, string $field): object + { + return $this->useCallback( + fn(bool $target_value, bool $other_value): bool => $target_value || $other_value, + $target, + $other, + $field + ); + } + + /** + * Perform a merge of comma separated lists from the target and the other entity for the given field. + * The values are merged and duplicates are removed. + * @param object $target + * @param object $other + * @param string $field + * @return object + */ + protected function mergeTags(object $target, object $other, string $field, string $separator = ','): object + { + return $this->useCallback( + function (string|null $t, string|null $o) use ($separator): string { + //Explode the strings into arrays + $t_array = explode($separator, $t ?? ''); + $o_array = explode($separator, $o ?? ''); + + //Merge the arrays and remove duplicates + $tmp = array_unique(array_merge($t_array, $o_array)); + + //Implode the array back to a string + return implode($separator, $tmp); + }, + $target, + $other, + $field + ); + } + + /** + * Merge the collections from the target and the other entity for the given field and put all items into the target collection. + * @param object $target + * @param object $other + * @param string $field + * @param callable|null $equal_fn A function, which checks if two items are equal. The signature is: function(object $target, object other): bool. + * Return true if the items are equal, false otherwise. If two items are equal, the item from the other collection is not added to the target collection. + * If null, the items are compared by (instance) identity. + * @return object + */ + protected function mergeCollections(object $target, object $other, string $field, ?callable $equal_fn = null): object + { + $target_collection = $this->property_accessor->getValue($target, $field); + $other_collection = $this->property_accessor->getValue($other, $field); + + if (!$target_collection instanceof Collection) { + throw new \InvalidArgumentException("The target field $field is not a collection"); + } + + //Clone the items from the other collection + $clones = []; + foreach ($other_collection as $item) { + //Check if the item is already in the target collection + if ($equal_fn !== null) { + foreach ($target_collection as $target_item) { + if ($equal_fn($target_item, $item)) { + continue 2; + } + } + } elseif ($target_collection->contains($item)) { + continue; + } + + $clones[] = clone $item; + } + + $tmp = array_merge($target_collection->toArray(), $clones); + + //Create a new collection with the clones and merge it into the target collection + $this->property_accessor->setValue($target, $field, $tmp); + + return $target; + } + + /** + * Merge the attachments from the target and the other entity. + * @param AttachmentContainingDBElement $target + * @param AttachmentContainingDBElement $other + * @return object + */ + protected function mergeAttachments(AttachmentContainingDBElement $target, AttachmentContainingDBElement $other): object + { + return $this->mergeCollections($target, $other, 'attachments', fn(Attachment $t, Attachment $o): bool => $t->getName() === $o->getName() + && $t->getAttachmentType() === $o->getAttachmentType() + && $t->getExternalPath() === $o->getExternalPath() + && $t->getInternalPath() === $o->getInternalPath()); + } + + /** + * Merge the parameters from the target and the other entity. + * @param AbstractStructuralDBElement|Part $target + * @param AbstractStructuralDBElement|Part $other + * @return object + */ + protected function mergeParameters(AbstractStructuralDBElement|Part $target, AbstractStructuralDBElement|Part $other): object + { + return $this->mergeCollections($target, $other, 'parameters', fn(AbstractParameter $t, AbstractParameter $o): bool => $t->getName() === $o->getName() + && $t->getSymbol() === $o->getSymbol() + && $t->getUnit() === $o->getUnit() + && $t->getValueMax() === $o->getValueMax() + && $t->getValueMin() === $o->getValueMin() + && $t->getValueTypical() === $o->getValueTypical() + && $t->getValueText() === $o->getValueText() + && $t->getGroup() === $o->getGroup()); + } + + /** + * Check if the two strings have equal content. + * This method is case-insensitive and ignores whitespace. + * @param string|\Stringable $t + * @param string|\Stringable $o + * @return bool + */ + protected function areStringsEqual(string|\Stringable $t, string|\Stringable $o): bool + { + $t_str = u($t)->trim()->folded(); + $o_str = u($o)->trim()->folded(); + + return $t_str->equalsTo($o_str); + } + + /** + * Merge the text from the target and the other entity for the given field by attaching the other text to the target text via the given separator. + * For example, if the target text is "Hello" and the other text is "World", the result is "Hello / World". + * If the text is the same in both entities, the target text is returned. + * @param object $target + * @param object $other + * @param string $field + * @param string $separator + * @return object + */ + protected function mergeTextWithSeparator(object $target, object $other, string $field, string $separator = ' / '): object + { + return $this->useCallback( + function (string $t, string $o) use ($separator): string { + //Check if the strings are equal + if ($this->areStringsEqual($t, $o)) { + return $t; + } + + //Skip empty strings + if (trim($t) === '') { + return trim($o); + } + if (trim($o) === '') { + return trim($t); + } + + return trim($t) . $separator . trim($o); + }, + $target, + $other, + $field + ); + } + + /** + * Merge two comments from the target and the other entity for the given field. + * The comments of the both entities get concated, while the second part get a headline with the name of the old part. + * @param AbstractNamedDBElement $target + * @param AbstractNamedDBElement $other + * @param string $field + * @return object + */ + protected function mergeComment(AbstractNamedDBElement $target, AbstractNamedDBElement $other, string $field = 'comment'): object + { + return $this->useCallback( + function (string $t, string $o) use ($other): string { + //Check if the strings are equal + if ($this->areStringsEqual($t, $o)) { + return $t; + } + + //Skip empty strings + if (trim($t) === '') { + return trim($o); + } + if (trim($o) === '') { + return trim($t); + } + + return sprintf("%s\n\n%s:\n%s", + trim($t), + $other->getName(), + trim($o) + ); + }, + $target, + $other, + $field + ); + } +} \ No newline at end of file diff --git a/src/Services/EntityMergers/Mergers/EntityMergerInterface.php b/src/Services/EntityMergers/Mergers/EntityMergerInterface.php new file mode 100644 index 00000000..046fc0ea --- /dev/null +++ b/src/Services/EntityMergers/Mergers/EntityMergerInterface.php @@ -0,0 +1,58 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\EntityMergers\Mergers; + + +use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; + +/** + * @template T of object + */ +#[AutoconfigureTag('app.entity_merger')] +interface EntityMergerInterface +{ + /** + * Determines if this merger supports merging the other entity into the target entity. + * @param object $target + * @phpstan-param T $target + * @param object $other + * @phpstan-param T $other + * @param array $context + * @return bool True if this merger supports merging the other entity into the target entity, false otherwise + */ + public function supports(object $target, object $other, array $context = []): bool; + + /** + * Merge the other entity into the target entity. + * The target entity will be modified and returned. + * @param object $target + * @phpstan-param T $target + * @param object $other + * @phpstan-param T $other + * @param array $context + * @phpstan-return T + * @return object + */ + public function merge(object $target, object $other, array $context = []): object; +} \ No newline at end of file diff --git a/src/Services/EntityMergers/Mergers/PartMerger.php b/src/Services/EntityMergers/Mergers/PartMerger.php new file mode 100644 index 00000000..d1f5c137 --- /dev/null +++ b/src/Services/EntityMergers/Mergers/PartMerger.php @@ -0,0 +1,187 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\EntityMergers\Mergers; + +use App\Entity\Parts\InfoProviderReference; +use App\Entity\Parts\ManufacturingStatus; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartAssociation; +use App\Entity\PriceInformations\Orderdetail; + +/** + * This class merges two parts together. + * + * @implements EntityMergerInterface + * @see \App\Tests\Services\EntityMergers\Mergers\PartMergerTest + */ +class PartMerger implements EntityMergerInterface +{ + + use EntityMergerHelperTrait; + + public function supports(object $target, object $other, array $context = []): bool + { + return $target instanceof Part && $other instanceof Part; + } + + public function merge(object $target, object $other, array $context = []): Part + { + if (!$target instanceof Part || !$other instanceof Part) { + throw new \InvalidArgumentException('The target and the other entity must be instances of Part'); + } + + //Merge basic fields + $this->mergeTextWithSeparator($target, $other, 'name'); + $this->mergeTextWithSeparator($target, $other, 'description'); + $this->mergeComment($target, $other); + $this->useOtherValueIfNotEmtpy($target, $other, 'manufacturer_product_url'); + $this->useOtherValueIfNotEmtpy($target, $other, 'manufacturer_product_number'); + $this->useOtherValueIfNotEmtpy($target, $other, 'mass'); + $this->useOtherValueIfNotEmtpy($target, $other, 'ipn'); + + //Merge relations to other entities + $this->useOtherValueIfNotNull($target, $other, 'manufacturer'); + $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'); + + //We assume that a part needs review and is a favorite if one of the parts is + $this->useTrueValue($target, $other, 'needs_review'); + $this->useTrueValue($target, $other, 'favorite'); + + //Merge the tags using the tag merger + $this->mergeTags($target, $other, 'tags'); + + //Merge manufacturing status + $this->useCallback(function (?ManufacturingStatus $t, ?ManufacturingStatus $o): ManufacturingStatus { + //Use the other value, if the target value is not set + if ($t === ManufacturingStatus::NOT_SET || $t === null) { + return $o ?? ManufacturingStatus::NOT_SET; + } + + return $t; + }, $target, $other, 'manufacturing_status'); + + //Merge provider reference + $this->useCallback(function (InfoProviderReference $t, InfoProviderReference $o): InfoProviderReference { + if (!$t->isProviderCreated() && $o->isProviderCreated()) { + return $o; + } + return $t; + }, $target, $other, 'providerReference'); + + //Merge the collections + $this->mergeCollectionFields($target, $other, $context); + + return $target; + } + + 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(); + } + + private function mergeCollectionFields(Part $target, Part $other, array $context): void + { + /******************************************************************************** + * Merge collections + ********************************************************************************/ + + //Lots from different parts are never considered equal, so we just merge them together + $this->mergeCollections($target, $other, 'partLots'); + $this->mergeAttachments($target, $other); + $this->mergeParameters($target, $other); + + //Merge the associations + $this->mergeCollections($target, $other, 'associated_parts_as_owner', $this->comparePartAssociations(...)); + + //We have to recreate the associations towards the other part, as they are not created by the merger + foreach ($other->getAssociatedPartsAsOther() as $association) { + //Clone the association + $clone = clone $association; + //Set the target part as the other part + $clone->setOther($target); + $owner = $clone->getOwner(); + if (!$owner) { + continue; + } + //Ensure that the association is not already present + foreach ($owner->getAssociatedPartsAsOwner() as $existing_association) { + if ($this->comparePartAssociations($existing_association, $clone)) { + continue 2; + } + } + + //Add the association to the owner + $owner->addAssociatedPartsAsOwner($clone); + } + + // Merge orderdetails, considering same supplier+part number as duplicates + $this->mergeCollections($target, $other, 'orderdetails', function (Orderdetail $t, Orderdetail $o) { + // If supplier and part number match, merge the orderdetails + if ($t->getSupplier() === $o->getSupplier() && $t->getSupplierPartNr() === $o->getSupplierPartNr()) { + // Update URL if target doesn't have one + if (empty($t->getSupplierProductUrl(false)) && !empty($o->getSupplierProductUrl(false))) { + $t->setSupplierProductUrl($o->getSupplierProductUrl(false)); + } + // Merge price details: add new ones, update empty ones, keep existing non-empty ones + foreach ($o->getPricedetails() as $otherPrice) { + $found = false; + foreach ($t->getPricedetails() as $targetPrice) { + if ($targetPrice->getMinDiscountQuantity() === $otherPrice->getMinDiscountQuantity() + && $targetPrice->getCurrency() === $otherPrice->getCurrency()) { + // Only update price if the existing one is zero/empty (most logical) + if ($targetPrice->getPrice()->isZero()) { + $targetPrice->setPrice($otherPrice->getPrice()); + $targetPrice->setPriceRelatedQuantity($otherPrice->getPriceRelatedQuantity()); + } + $found = true; + break; + } + } + // Add completely new price tiers + if (!$found) { + $clonedPrice = clone $otherPrice; + $clonedPrice->setOrderdetail($t); + $t->addPricedetail($clonedPrice); + } + } + return true; // Consider them equal so the other one gets skipped + } + 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) { + foreach ($orderdetail->getPricedetails() as $pricedetail) { + $pricedetail->setOrderdetail($orderdetail); + } + } + } +} \ No newline at end of file diff --git a/src/Services/EntityURLGenerator.php b/src/Services/EntityURLGenerator.php index 26807f69..91e271cc 100644 --- a/src/Services/EntityURLGenerator.php +++ b/src/Services/EntityURLGenerator.php @@ -27,6 +27,7 @@ use App\Entity\Attachments\AttachmentType; use App\Entity\Attachments\PartAttachment; use App\Entity\Base\AbstractDBElement; use App\Entity\Parameters\PartParameter; +use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parts\Category; @@ -35,7 +36,7 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; @@ -44,9 +45,7 @@ use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use App\Exceptions\EntityNotSupportedException; use App\Services\Attachments\AttachmentURLGenerator; -use DateTime; use function array_key_exists; -use function get_class; use InvalidArgumentException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -102,13 +101,14 @@ class EntityURLGenerator Project::class => 'project_edit', Supplier::class => 'supplier_edit', Manufacturer::class => 'manufacturer_edit', - Storelocation::class => 'store_location_edit', + StorageLocation::class => 'store_location_edit', Footprint::class => 'footprint_edit', User::class => 'user_edit', Currency::class => 'currency_edit', MeasurementUnit::class => 'measurement_unit_edit', Group::class => 'group_edit', LabelProfile::class => 'label_profile_edit', + PartCustomState::class => 'part_custom_state_edit', ]; try { @@ -158,25 +158,34 @@ class EntityURLGenerator 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!'); - } - - return $this->attachmentURLGenerator->getDownloadURL($entity); + if (!($entity instanceof Attachment)) { + throw new EntityNotSupportedException(sprintf('The given entity is not supported yet! Passed class type: %s', $entity::class)); } - //Otherwise throw an error - throw new EntityNotSupportedException(sprintf('The given entity is not supported yet! Passed class type: %s', $entity::class)); + if ($entity->hasInternal()) { + return $this->attachmentURLGenerator->getInternalDownloadURL($entity); + } + + if($entity->hasExternal()) { + return $entity->getExternalPath(); + } + + throw new \RuntimeException('Attachment has not internal or external path!'); } /** @@ -199,13 +208,14 @@ class EntityURLGenerator Project::class => 'project_info', Supplier::class => 'supplier_edit', Manufacturer::class => 'manufacturer_edit', - Storelocation::class => 'store_location_edit', + StorageLocation::class => 'store_location_edit', Footprint::class => 'footprint_edit', User::class => 'user_edit', Currency::class => 'currency_edit', 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()]); @@ -229,13 +239,14 @@ class EntityURLGenerator Project::class => 'project_edit', Supplier::class => 'supplier_edit', Manufacturer::class => 'manufacturer_edit', - Storelocation::class => 'store_location_edit', + StorageLocation::class => 'store_location_edit', Footprint::class => 'footprint_edit', User::class => 'user_edit', Currency::class => 'currency_edit', 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()]); @@ -260,13 +271,14 @@ class EntityURLGenerator Project::class => 'project_new', Supplier::class => 'supplier_new', Manufacturer::class => 'manufacturer_new', - Storelocation::class => 'store_location_new', + StorageLocation::class => 'store_location_new', Footprint::class => 'footprint_new', User::class => 'user_new', Currency::class => 'currency_new', 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)); @@ -291,13 +303,14 @@ class EntityURLGenerator Project::class => 'device_clone', Supplier::class => 'supplier_clone', Manufacturer::class => 'manufacturer_clone', - Storelocation::class => 'store_location_clone', + StorageLocation::class => 'store_location_clone', Footprint::class => 'footprint_clone', User::class => 'user_clone', Currency::class => 'currency_clone', 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()]); @@ -321,7 +334,7 @@ class EntityURLGenerator Footprint::class => 'part_list_footprint', Manufacturer::class => 'part_list_manufacturer', Supplier::class => 'part_list_supplier', - Storelocation::class => 'part_list_store_location', + StorageLocation::class => 'part_list_store_location', ]; return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]); @@ -336,13 +349,14 @@ class EntityURLGenerator Project::class => 'project_delete', Supplier::class => 'supplier_delete', Manufacturer::class => 'manufacturer_delete', - Storelocation::class => 'store_location_delete', + StorageLocation::class => 'store_location_delete', Footprint::class => 'footprint_delete', User::class => 'user_delete', Currency::class => 'currency_delete', 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()]); @@ -362,11 +376,7 @@ class EntityURLGenerator */ protected function mapToController(array $map, string|AbstractDBElement $entity): string { - if (is_string($entity)) { //If a class name was already passed, then use it directly - $class = $entity; - } else { //Otherwise get the class name from the entity - $class = $entity::class; - } + $class = is_string($entity) ? $entity : $entity::class; //Check if we have an direct mapping for the given class if (!array_key_exists($class, $map)) { diff --git a/src/Services/Formatters/MoneyFormatter.php b/src/Services/Formatters/MoneyFormatter.php index d49b77cf..505752c3 100644 --- a/src/Services/Formatters/MoneyFormatter.php +++ b/src/Services/Formatters/MoneyFormatter.php @@ -23,6 +23,7 @@ declare(strict_types=1); 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,8 +46,8 @@ class MoneyFormatter */ public function format(string|float $value, ?Currency $currency = null, int $decimals = 5, bool $show_all_digits = false): string { - $iso_code = $this->base_currency; - if ($currency instanceof Currency && ($currency->getIsoCode() !== null && $currency->getIsoCode() !== '')) { + $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 a6325987..b83501fa 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 89b62660..e511c04d 100644 --- a/src/Services/ImportExportSystem/BOMImporter.php +++ b/src/Services/ImportExportSystem/BOMImporter.php @@ -22,10 +22,13 @@ declare(strict_types=1); */ 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; @@ -36,22 +39,33 @@ class BOMImporter { private const MAP_KICAD_PCB_FIELDS = [ - 'ID' => 'Id', - 'Bezeichner' => 'Designator', - 'Footprint' => 'Package', - 'Stückzahl' => 'Quantity', - 'Bezeichnung' => 'Designation', - 'Anbieter und Referenz' => 'Supplier and ref', + 0 => 'Id', + 1 => 'Designator', + 2 => 'Package', + 3 => 'Quantity', + 4 => 'Designation', + 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 @@ class BOMImporter 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,14 +126,15 @@ class BOMImporter $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 = Reader::fromString($data); $csv->setDelimiter(';'); $csv->setHeaderOffset(0); @@ -110,20 +142,20 @@ class BOMImporter foreach ($csv->getRecords() as $offset => $entry) { //Translate the german field names to english - $entry = array_combine(array_map(static fn($key) => self::MAP_KICAD_PCB_FIELDS[$key] ?? $key, array_keys($entry)), $entry); + $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(); @@ -137,4 +169,562 @@ class BOMImporter return $bom_entries; } + + /** + * Validate KiCad PCB data + */ + private function validateKiCADPCB(string $data): array + { + $csv = Reader::fromString($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::fromString($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 + * @return array + */ + private function normalizeColumnNames(array $entry): array + { + $out = []; + + //Map the entry order to the correct column names + foreach (array_values($entry) as $index => $field) { + if ($index > 5) { + break; + } + + //@phpstan-ignore-next-line We want to keep this check just to be safe when something changes + $new_index = self::MAP_KICAD_PCB_FIELDS[$index] ?? throw new \UnexpectedValueException('Invalid field index!'); + $out[$new_index] = $field; + } + + 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::fromString($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 00000000..74f81fe3 --- /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 50b6b7cc..70feb8e6 100644 --- a/src/Services/ImportExportSystem/EntityExporter.php +++ b/src/Services/ImportExportSystem/EntityExporter.php @@ -23,8 +23,13 @@ declare(strict_types=1); namespace App\Services\ImportExportSystem; use App\Entity\Base\AbstractNamedDBElement; +use App\Entity\Base\AbstractStructuralDBElement; +use App\Helpers\FilenameSanatizer; +use App\Serializer\APIPlatform\SkippableItemNormalizer; use Symfony\Component\OptionsResolver\OptionsResolver; use InvalidArgumentException; +use Symfony\Component\Serializer\Exception\CircularReferenceException; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use function is_array; use ReflectionClass; use ReflectionException; @@ -33,6 +38,9 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\Serializer\SerializerInterface; use function Symfony\Component\String\u; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\Writer\Xls; /** * Use this class to export an entity to multiple file formats. @@ -47,7 +55,7 @@ class EntityExporter 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'); @@ -83,22 +91,115 @@ class EntityExporter $options = $resolver->resolve($options); + //Handle Excel formats by converting from CSV + if (in_array($options['format'], ['xlsx', 'xls'], true)) { + return $this->exportToExcel($entities, $options); + } + //If include children is set, then we need to add the include_children group $groups = [$options['level']]; if ($options['include_children']) { $groups[] = 'include_children'; } - return $this->serializer->serialize($entities, $options['format'], + return $this->serializer->serialize( + $entities, + $options['format'], [ 'groups' => $groups, 'as_collection' => true, 'csv_delimiter' => $options['csv_delimiter'], 'xml_root_node_name' => 'PartDBExport', + 'partdb_export' => true, + //Skip the item normalizer, so that we dont get IRIs in the output + SkippableItemNormalizer::DISABLE_ITEM_NORMALIZER => true, + //Handle circular references + AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => $this->handleCircularReference(...), ] ); } + private function handleCircularReference(object $object): string + { + if ($object instanceof AbstractStructuralDBElement) { + return $object->getFullPath("->"); + } elseif ($object instanceof AbstractNamedDBElement) { + return $object->getName(); + } elseif ($object instanceof \Stringable) { + return $object->__toString(); + } + + throw new CircularReferenceException('Circular reference detected for object of type ' . get_class($object)); + } + + /** + * Exports 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; + } + /** * Exports an Entity or an array of entities to multiple file formats. * @@ -114,7 +215,7 @@ class EntityExporter $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)) { @@ -133,19 +234,15 @@ class EntityExporter //Determine the content type for the response - //Plain text should work for all types - $content_type = 'text/plain'; - //Try to use better content types based on the format $format = $options['format']; - 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. @@ -163,7 +260,10 @@ class EntityExporter $level = $options['level']; - $filename = 'export_'.$entity_name.'_'.$level.'.'.$format; + $filename = "export_{$entity_name}_{$level}.{$format}"; + + //Sanitize the filename + $filename = FilenameSanatizer::sanitizeFilename($filename); // Create the disposition of the file $disposition = $response->headers->makeDisposition( diff --git a/src/Services/ImportExportSystem/EntityImporter.php b/src/Services/ImportExportSystem/EntityImporter.php index 0b4e0f17..a89be9dc 100644 --- a/src/Services/ImportExportSystem/EntityImporter.php +++ b/src/Services/ImportExportSystem/EntityImporter.php @@ -26,9 +26,10 @@ use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Parts\Category; use App\Entity\Parts\Part; -use Composer\Semver\Constraint\Constraint; +use App\Repository\StructuralDBElementRepository; +use App\Serializer\APIPlatform\SkippableItemNormalizer; use Symfony\Component\Validator\ConstraintViolationList; -use Symplify\EasyCodingStandard\ValueObject\Option; +use Symfony\Component\Validator\ConstraintViolationListInterface; use function count; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; @@ -37,29 +38,46 @@ use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; +use PhpOffice\PhpSpreadsheet\IOFactory; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use Psr\Log\LoggerInterface; /** * @see \App\Tests\Services\ImportExportSystem\EntityImporterTest */ class EntityImporter { - public function __construct(protected SerializerInterface $serializer, protected EntityManagerInterface $em, protected ValidatorInterface $validator) + + /** + * The encodings that are supported by the importer, and that should be autodeceted. + */ + private const ENCODINGS = ["ASCII", "UTF-8", "ISO-8859-1", "ISO-8859-15", "Windows-1252", "UTF-16", "UTF-32"]; + + public function __construct(protected SerializerInterface $serializer, protected EntityManagerInterface $em, protected ValidatorInterface $validator, 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 * @param string $class_name The name of the class for which the entities should be created + * @phpstan-param class-string $class_name * @param AbstractStructuralDBElement|null $parent the element which will be used as parent element for new elements * @param array $errors an associative array containing all validation errors + * @param-out list $errors * - * @return AbstractStructuralDBElement[] An array containing all valid imported entities (with the type $class_name) + * @return AbstractNamedDBElement[] An array containing all valid imported entities (with the type $class_name) + * @return T[] */ public function massCreation(string $lines, string $class_name, ?AbstractStructuralDBElement $parent = null, array &$errors = []): array { + //Try to detect the text encoding of the data and convert it to UTF-8 + $lines = mb_convert_encoding($lines, 'UTF-8', mb_detect_encoding($lines, self::ENCODINGS)); + //Expand every line to a single entry: $names = explode("\n", $lines); @@ -70,6 +88,13 @@ class EntityImporter throw new InvalidArgumentException('$parent must have the same type as specified in $class_name!'); } + //Ensure that parent is already persisted. Otherwise the getNewEntityFromPath function will not work. + if ($parent !== null && $parent->getID() === null) { + throw new InvalidArgumentException('The parent must persisted to database!'); + } + + $repo = $this->em->getRepository($class_name); + $errors = []; $valid_entities = []; @@ -79,10 +104,10 @@ class EntityImporter $indentations = [0]; foreach ($names as $name) { - //Count intendation level (whitespace characters at the beginning of the line) - $identSize = strlen($name)-strlen(ltrim($name)); + //Count indentation level (whitespace characters at the beginning of the line) + $identSize = strlen($name) - strlen(ltrim($name)); - //If the line is intendet more than the last line, we have a new parent element + //If the line is intended more than the last line, we have a new parent element if ($identSize > end($indentations)) { $current_parent = $last_element; //Add the new indentation level to the stack @@ -99,31 +124,50 @@ class EntityImporter //Skip empty lines (StrucuralDBElements must have a name) continue; } + /** @var AbstractStructuralDBElement $entity */ - //Create new element with given name - $entity = new $class_name(); - $entity->setName($name); - //Only set the parent if the entity is a StructuralDBElement - if ($entity instanceof AbstractStructuralDBElement) { - $entity->setParent($current_parent); + //Create new element with given name. Using the function from the repository, to correctly reuse existing elements + + if ($current_parent instanceof AbstractStructuralDBElement) { + $new_path = $current_parent->getFullPath("->") . '->' . $name; + } else { + $new_path = $name; } + //We can only use the getNewEntityFromPath function, if the repository is a StructuralDBElementRepository + if ($repo instanceof StructuralDBElementRepository) { + $entities = $repo->getNewEntityFromPath($new_path); + 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, SORT_REGULAR)); } /** @@ -131,10 +175,14 @@ class EntityImporter * @param string $data The serialized data which should be imported * @param array $options The options for the import process * @param array $errors An array which will be filled with the validation errors, if any occurs during import + * @param-out array $errors * @return array An array containing all valid imported entities */ public function importString(string $data, array $options = [], array &$errors = []): array { + //Try to detect the text encoding of the data and convert it to UTF-8 + $data = mb_convert_encoding($data, 'UTF-8', mb_detect_encoding($data, self::ENCODINGS)); + $resolver = new OptionsResolver(); $this->configureOptions($resolver); $options = $resolver->resolve($options); @@ -150,13 +198,20 @@ class EntityImporter } //The [] behind class_name denotes that we expect an array. - $entities = $this->serializer->deserialize($data, $options['class'].'[]', $options['format'], + $entities = $this->serializer->deserialize( + $data, + $options['class'] . '[]', + $options['format'], [ 'groups' => $groups, 'csv_delimiter' => $options['csv_delimiter'], 'create_unknown_datastructures' => $options['create_unknown_datastructures'], 'path_delimiter' => $options['path_delimiter'], - ]); + 'partdb_import' => true, + //Disable API Platform normalizer, as we don't want to use it here + SkippableItemNormalizer::DISABLE_ITEM_NORMALIZER => true, + ] + ); //Ensure we have an array of entity elements. if (!is_array($entities)) { @@ -188,12 +243,21 @@ class EntityImporter //Iterate over each $entity write it to DB. foreach ($entities as $key => $entity) { + //Ensure that entity is a NamedDBElement + if (!$entity instanceof AbstractNamedDBElement) { + throw new \RuntimeException("Encountered an entity that is not a NamedDBElement!"); + } + //Validate entity $tmp = $this->validator->validate($entity); if (count($tmp) > 0) { //Log validation errors to global log. $name = $entity instanceof AbstractStructuralDBElement ? $entity->getFullPath() : $entity->getName(); + if (trim($name) === '') { + $name = 'Row ' . (string) $key; + } + $errors[$name] = [ 'violations' => $tmp, 'entity' => $entity, @@ -222,7 +286,7 @@ class EntityImporter 'path_delimiter' => '->', //The delimiter used to separate the path elements in the name of a structural element ]); - $resolver->setAllowedValues('format', ['csv', 'json', 'xml', 'yaml']); + $resolver->setAllowedValues('format', ['csv', 'json', 'xml', 'yaml', 'xlsx', 'xls']); $resolver->setAllowedTypes('csv_delimiter', 'string'); $resolver->setAllowedTypes('preserve_children', 'bool'); $resolver->setAllowedTypes('class', 'string'); @@ -238,9 +302,9 @@ class EntityImporter * * @param File $file the file that should be used for importing * @param array $options options for the import process - * @param AbstractNamedDBElement[] $entities The imported entities are returned in this array + * @param-out AbstractNamedDBElement[] $entities The imported entities are returned in this array * - * @return array An associative array containing an ConstraintViolationList and the entity name as key are returned, + * @return array An associative array containing an ConstraintViolationList and the entity name as key are returned, * if an error happened during validation. When everything was successfully, the array should be empty. */ public function importFileAndPersistToDB(File $file, array $options = [], array &$entities = []): array @@ -272,11 +336,39 @@ class EntityImporter * * @param File $file the file that should be used for importing * @param array $options options for the import process + * @param-out array $errors * - * @return array an array containing the deserialized elements + * @return AbstractNamedDBElement[] an array containing the deserialized elements */ 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); } @@ -296,17 +388,110 @@ class EntityImporter 'xml' => 'xml', 'csv', 'tsv' => 'csv', 'yaml', 'yml' => 'yaml', + 'xlsx' => 'xlsx', + 'xls' => 'xls', default => null, }; } + /** + * Converts Excel file to CSV format using PhpSpreadsheet. + * + * @param File $file The Excel file to convert + * @param string $delimiter The CSV delimiter to use + * + * @return string The CSV data as string + */ + protected function convertExcelToCsv(File $file, string $delimiter = ';'): string + { + try { + $this->logger->debug('Loading Excel file', ['path' => $file->getPathname()]); + $spreadsheet = IOFactory::load($file->getPathname()); + $worksheet = $spreadsheet->getActiveSheet(); + + $csvData = []; + $highestRow = $worksheet->getHighestRow(); + $highestColumn = $worksheet->getHighestColumn(); + + $this->logger->debug('Excel file dimensions', [ + 'rows' => $highestRow, + 'columns_detected' => $highestColumn, + 'worksheet_title' => $worksheet->getTitle() + ]); + + $highestColumnIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($highestColumn); + + for ($row = 1; $row <= $highestRow; $row++) { + $rowData = []; + + // Read all columns using numeric index + for ($colIndex = 1; $colIndex <= $highestColumnIndex; $colIndex++) { + $col = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($colIndex); + try { + $cellValue = $worksheet->getCell("{$col}{$row}")->getCalculatedValue(); + $rowData[] = $cellValue ?? ''; + + } catch (\Exception $e) { + $this->logger->warning('Error reading cell value', [ + 'cell' => "{$col}{$row}", + 'error' => $e->getMessage() + ]); + $rowData[] = ''; + } + } + + $csvRow = implode($delimiter, array_map(function ($value) use ($delimiter) { + $value = (string) $value; + if (strpos($value, $delimiter) !== false || strpos($value, '"') !== false || strpos($value, "\n") !== false) { + return '"' . str_replace('"', '""', $value) . '"'; + } + return $value; + }, $rowData)); + + $csvData[] = $csvRow; + + // Log first few rows for debugging + if ($row <= 3) { + $this->logger->debug("Row {$row} converted", [ + 'original_data' => $rowData, + 'csv_row' => $csvRow, + 'first_cell_raw' => $worksheet->getCell("A{$row}")->getValue(), + 'first_cell_calculated' => $worksheet->getCell("A{$row}")->getCalculatedValue() + ]); + } + } + + $result = implode("\n", $csvData); + + $this->logger->info('Excel to CSV conversion successful', [ + 'total_rows' => count($csvData), + 'total_characters' => strlen($result) + ]); + + $this->logger->debug('Full CSV data', [ + 'csv_data' => $result + ]); + + return $result; + + } catch (\Exception $e) { + $this->logger->error('Failed to convert Excel to CSV', [ + 'file' => $file->getFilename(), + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + throw $e; + } + } + + /** * This functions corrects the parent setting based on the children value of the parent. * * @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 4f9acbca..ec23d34b 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php @@ -22,27 +22,20 @@ declare(strict_types=1); */ namespace App\Services\ImportExportSystem\PartKeeprImporter; -use App\Doctrine\Purger\ResetAutoIncrementORMPurger; use App\Entity\Attachments\FootprintAttachment; use App\Entity\Attachments\ManufacturerAttachment; -use App\Entity\Attachments\StorelocationAttachment; -use App\Entity\Base\AbstractDBElement; -use App\Entity\Base\AbstractStructuralDBElement; -use App\Entity\Contracts\TimeStampableInterface; -use App\Entity\Parameters\PartParameter; +use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\Part; -use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\PartCustomState; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; -use function \count; +use function count; /** * This service is used to import the datastructures (categories, manufacturers, etc.) from a PartKeepr export. @@ -156,6 +149,26 @@ class PKDatastructureImporter return is_countable($partunit_data) ? count($partunit_data) : 0; } + public function importPartCustomStates(array $data): int + { + if (!isset($data['partcustomstate'])) { + return 0; //Not all PartKeepr installations have custom states + } + + $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'])) { @@ -263,9 +276,9 @@ class PKDatastructureImporter public function importStorelocations(array $data): int { - $count = $this->importElementsWithCategory($data, Storelocation::class, 'storagelocation'); + $count = $this->importElementsWithCategory($data, StorageLocation::class, 'storagelocation'); - $this->importAttachments($data, 'storagelocationimage', Storelocation::class, 'storageLocation_id', StorelocationAttachment::class); + $this->importAttachments($data, 'storagelocationimage', StorageLocation::class, 'storageLocation_id', StorageLocationAttachment::class); return $count; } diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php index 70237114..64127341 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php @@ -30,7 +30,7 @@ use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Contracts\TimeStampableInterface; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; /** @@ -105,7 +105,7 @@ trait PKImportHelperTrait //Next comes the filename plus extension $path .= '/'.$attachment_row['filename'].'.'.$attachment_row['extension']; - $attachment->setPath($path); + $attachment->setInternalPath($path); return $attachment; } @@ -150,6 +150,11 @@ trait PKImportHelperTrait $target->addAttachment($attachment); $this->em->persist($attachment); + + //If the attachment is an image, and the target has no master picture yet, set it + if ($attachment->isPicture() && $target->getMasterPictureAttachment() === null) { + $target->setMasterPictureAttachment($attachment); + } } $this->em->flush(); @@ -205,14 +210,10 @@ trait PKImportHelperTrait */ protected function setIDOfEntity(AbstractDBElement $element, int|string $id): void { - if (!is_int($id) && !is_string($id)) { - throw new \InvalidArgumentException('ID must be an integer or string'); - } - $id = (int) $id; $metadata = $this->em->getClassMetadata($element::class); - $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE); + $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE); $metadata->setIdGenerator(new AssignedGenerator()); $metadata->setIdentifierValues($element, ['id' => $id]); } @@ -222,10 +223,10 @@ trait PKImportHelperTrait * @return void * @throws \Exception */ - protected function setCreationDate(TimeStampableInterface $entity, ?string $datetime_str) + protected function setCreationDate(TimeStampableInterface $entity, ?string $datetime_str): void { - if ($datetime_str) { - $date = new \DateTime($datetime_str); + if ($datetime_str !== null && $datetime_str !== '' && $datetime_str !== '0000-00-00 00:00:00') { + $date = new \DateTimeImmutable($datetime_str); } else { $date = null; //Null means "now" at persist time } @@ -235,4 +236,27 @@ trait PKImportHelperTrait $property->setAccessible(true); $property->setValue($entity, $date); } + + /** + * Gets the SI prefix factor for the given prefix ID. + * Used to convert a value from the PartKeepr database to the PartDB database. + * @param array $data + * @param int $prefix_id + * @return float + */ + protected function getSIPrefixFactor(array $data, int $prefix_id): float + { + if ($prefix_id === 0) { + return 1.0; + } + + $prefixes = $data['siprefix']; + foreach ($prefixes as $prefix) { + if ((int) $prefix['id'] === $prefix_id) { + return (int)$prefix['base'] ** (int)$prefix['exponent']; + } + } + + throw new \RuntimeException(sprintf('Could not find SI prefix with ID %s', $prefix_id)); + } } diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php index b8e8272e..fafde29a 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php @@ -116,7 +116,7 @@ class PKOptionalImporter //All imported users get assigned to the "PartKeepr Users" group $group_users = $this->em->find(Group::class, 3); $group = $this->em->getRepository(Group::class)->findOneBy(['name' => 'PartKeepr Users', 'parent' => $group_users]); - if (!$group) { + if ($group === null) { $group = new Group(); $group->setName('PartKeepr Users'); $group->setParent($group_users); diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php index ab00ba5b..cab5a49b 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php @@ -30,11 +30,12 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; 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,10 +91,15 @@ class PKPartImporter $this->setAssociationField($entity, 'partUnit', MeasurementUnit::class, $part['partUnit_id']); } + if (isset($part['partCustomState_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)); - $this->setAssociationField($lot, 'storage_location', Storelocation::class, $part['storageLocation_id']); + $this->setAssociationField($lot, 'storage_location', StorageLocation::class, $part['storageLocation_id']); $entity->addPartLot($lot); //For partCondition, productionsRemarks and Status, create a custom parameter @@ -173,20 +179,20 @@ class PKPartImporter $entity->setName($name); $entity->setValueText($partparameter['stringValue'] ?? ''); - if ($partparameter['unit_id'] === null) { + if ($partparameter['unit_id'] !== null && (int) $partparameter['unit_id'] !== 0) { $entity->setUnit($this->getUnitSymbol($data, (int)$partparameter['unit_id'])); } else { $entity->setUnit(""); } - if ($partparameter['normalizedMinValue'] !== null) { - $entity->setValueMin((float) $partparameter['normalizedMinValue']); + if ($partparameter['value'] !== null) { + $entity->setValueTypical((float) $partparameter['value'] * $this->getSIPrefixFactor($data, (int) $partparameter['siPrefix_id'])); } - if ($partparameter['normalizedValue'] !== null) { - $entity->setValueTypical((float) $partparameter['normalizedValue']); + if ($partparameter['minimumValue'] !== null) { + $entity->setValueMin((float) $partparameter['minimumValue'] * $this->getSIPrefixFactor($data, (int) $partparameter['minSiPrefix_id'])); } - if ($partparameter['normalizedMaxValue'] !== null) { - $entity->setValueMax((float) $partparameter['normalizedMaxValue']); + if ($partparameter['maximumValue'] !== null) { + $entity->setValueMax((float) $partparameter['maximumValue'] * $this->getSIPrefixFactor($data, (int) $partparameter['maxSiPrefix_id'])); } $part = $this->em->find(Part::class, (int) $partparameter['part_id']); @@ -210,7 +216,7 @@ class PKPartImporter $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; } @@ -218,7 +224,7 @@ class PKPartImporter 'iso_code' => $currency_iso_code, ]); - if (!$currency) { + if ($currency === null) { $currency = new Currency(); $currency->setIsoCode($currency_iso_code); $currency->setName(Currencies::getName($currency_iso_code)); @@ -255,7 +261,7 @@ class PKPartImporter } elseif (!empty($partdistributor['orderNumber']) && !empty($partdistributor['sku'])) { $spn = $partdistributor['orderNumber'] . ' (' . $partdistributor['sku'] . ')'; } else { - $spn = 'PartKeepr Import'; + $spn = ''; } $orderdetail = $this->em->getRepository(Orderdetail::class)->findOneBy([ @@ -265,7 +271,7 @@ class PKPartImporter ]); //When no orderdetail exists, create one - if (!$orderdetail) { + if ($orderdetail === null) { $orderdetail = new Orderdetail(); $orderdetail->setSupplier($supplier); $orderdetail->setSupplierpartnr($spn); @@ -273,8 +279,8 @@ class PKPartImporter $this->em->persist($orderdetail); } - //Add the price information to the orderdetail - if (!empty($partdistributor['price'])) { + //Add the price information to the orderdetail (only if the price is not zero, as this was a placeholder in PartKeepr) + if (!empty($partdistributor['price']) && !BigDecimal::of($partdistributor['price'])->isZero()) { $pricedetail = new Pricedetail(); $orderdetail->addPricedetail($pricedetail); //Partkeepr stores the price per item, we need to convert it to the price per packaging unit diff --git a/src/Services/InfoProviderSystem/BulkInfoProviderService.php b/src/Services/InfoProviderSystem/BulkInfoProviderService.php new file mode 100644 index 00000000..586fb873 --- /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 00000000..47d8ac69 --- /dev/null +++ b/src/Services/InfoProviderSystem/DTOs/BulkSearchFieldMappingDTO.php @@ -0,0 +1,108 @@ +. + */ + +declare(strict_types=1); + +namespace App\Services\InfoProviderSystem\DTOs; + +use App\Services\InfoProviderSystem\Providers\InfoProviderInterface; + +/** + * Represents a mapping between a part field and the info providers that should search in that field. + */ +readonly class BulkSearchFieldMappingDTO +{ + /** @var string[] $providers Array of provider keys to search with (e.g., ['digikey', 'farnell']) */ + public array $providers; + + /** + * @param string $field The field to search in (e.g., 'mpn', 'name', or supplier-specific fields like 'digikey_spn') + * @param string[]|InfoProviderInterface[] $providers Array of provider keys to search with (e.g., ['digikey', 'farnell']) + * @param int $priority Priority for this field mapping (1-10, lower numbers = higher priority) + */ + public function __construct( + public string $field, + array $providers = [], + public int $priority = 1 + ) { + if ($priority < 1 || $priority > 10) { + throw new \InvalidArgumentException('Priority must be between 1 and 10'); + } + + //Ensure that providers are provided as keys + foreach ($providers as &$provider) { + if ($provider instanceof InfoProviderInterface) { + $provider = $provider->getProviderKey(); + } + if (!is_string($provider)) { + throw new \InvalidArgumentException('Providers must be provided as strings or InfoProviderInterface instances'); + } + } + unset($provider); + $this->providers = $providers; + } + + /** + * Create a FieldMappingDTO from legacy array format. + * @param array{field: string, providers: string[], priority?: int} $data + */ + public static function fromSerializableArray(array $data): self + { + return new self( + field: $data['field'], + providers: $data['providers'] ?? [], + priority: $data['priority'] ?? 1 + ); + } + + /** + * Convert this DTO to the legacy array format for backwards compatibility. + * @return array{field: string, providers: string[], priority: int} + */ + public function toSerializableArray(): array + { + return [ + 'field' => $this->field, + 'providers' => $this->providers, + 'priority' => $this->priority, + ]; + } + + /** + * Check if this field mapping is for a supplier part number field. + */ + public function isSupplierPartNumberField(): bool + { + return str_ends_with($this->field, '_spn'); + } + + /** + * Get the supplier key from a supplier part number field. + * Returns null if this is not a supplier part number field. + */ + public function getSupplierKey(): ?string + { + if (!$this->isSupplierPartNumberField()) { + return null; + } + + return substr($this->field, 0, -4); + } +} diff --git a/src/Services/InfoProviderSystem/DTOs/BulkSearchPartResultDTO.php b/src/Services/InfoProviderSystem/DTOs/BulkSearchPartResultDTO.php new file mode 100644 index 00000000..d46624d4 --- /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 00000000..8614f4ec --- /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 00000000..58e9e240 --- /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 516ab949..84eed0c9 100644 --- a/src/Services/InfoProviderSystem/DTOs/FileDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/FileDTO.php @@ -26,17 +26,28 @@ namespace App\Services\InfoProviderSystem\DTOs; /** * This DTO represents a file that can be downloaded from a URL. * This could be a datasheet, a 3D model, a picture or similar. + * @see \App\Tests\Services\InfoProviderSystem\DTOs\FileDTOTest */ -class FileDTO +readonly class FileDTO { + /** + * @var string The URL where to get this file + */ + public string $url; + /** * @param string $url The URL where to get this file * @param string|null $name Optionally the name of this file */ public function __construct( - public readonly string $url, - public readonly ?string $name = null, - ) {} + string $url, + 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). + //Digikey provided some wrong URLs with a ^ in them, which is not a valid URL character. (https://github.com/Part-DB/Part-DB-server/issues/521) + $this->url = preg_replace_callback('/[^a-zA-Z0-9_\-.$+!*();\/?:@=&#%]/', static fn($matches) => rawurlencode($matches[0]), $url); + } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php b/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php index f2a0d978..f5868039 100644 --- a/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php @@ -26,25 +26,29 @@ namespace App\Services\InfoProviderSystem\DTOs; /** * This DTO represents a parameter of a part (similar to the AbstractParameter entity). * This could be a voltage, a current, a temperature or similar. + * @see \App\Tests\Services\InfoProviderSystem\DTOs\ParameterDTOTest */ -class ParameterDTO +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, ) { } /** * This function tries to decide on the value, if it is a numerical value (which is then stored in one of the value_*) fields) or a text value (which is stored in value_text). - * It is possible to give ranges like 1...2 here, which will be parsed as value_min: 1.0, value_max: 2.0. + * It is possible to give ranges like 1...2 (or 1~2) here, which will be parsed as value_min: 1.0, value_max: 2.0. + * + * For certain expressions (like ranges) the unit is automatically extracted from the value, if no unit is given + * @TODO Rework that, so that the difference between parseValueField and parseValueIncludingUnit is clearer or merge them * @param string $name * @param string|float $value * @param string|null $unit @@ -54,23 +58,66 @@ class ParameterDTO */ public static function parseValueField(string $name, string|float $value, ?string $unit = null, ?string $symbol = null, ?string $group = null): self { - if (is_float($value) || is_numeric($value)) { - return new self($name, value_typ: (float) $value, unit: $unit, symbol: $symbol, group: $group); + //If we encounter something like 2.5@text, then put the "@text" into text_value and continue with the number parsing + if (is_string($value) && preg_match('/^(.+)(@.+)$/', $value, $matches) === 1) { + $value = $matches[1]; + $value_text = $matches[2]; + } else { + $value_text = null; } - //Try to parse as range - if (str_contains($value, '...')) { - $parts = explode('...', $value); - if (count($parts) === 2) { + //If the value is just a number, we assume thats the typical value + if (is_float($value) || is_numeric($value)) { + return new self($name, value_text: $value_text, value_typ: (float) $value, unit: $unit, symbol: $symbol, + group: $group); + } - //Ensure that both parts are numerical - if (is_numeric($parts[0]) && is_numeric($parts[1])) { - return new self($name, value_min: (float) $parts[0], value_max: (float) $parts[1], unit: $unit, symbol: $symbol, group: $group); + //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) === '') { + [$number, $unit] = self::splitIntoValueAndUnit(ltrim($parts[0], " +")) ?? [$parts[0], null]; + } else { + $number = $parts[0]; + } + + // If the second part has some extra info, we'll save that into value_text + if (!empty($unit) && preg_match('/^(.+' . preg_quote($unit, '/') . ')\s*(.+)$/', $parts[1], $matches) > 0) { + $parts[1] = $matches[1]; + $value_text2 = $matches[2]; + } else { + $value_text2 = null; + } + [$number2, $unit2] = self::splitIntoValueAndUnit(ltrim($parts[1], " +")) ?? [$parts[1], $unit]; + + //If both parts have the same unit and both values are numerical, we'll save it as range + if ($unit === $unit2 && is_numeric($number) && is_numeric($number2)) { + return new self(name: $name, value_text: $value_text2, value_min: (float) $number, + value_max: (float) $number2, unit: $unit, symbol: $symbol, group: $group); } } + //If it's a plus/minus value, we'll also treat it as a range + } elseif (str_starts_with($value, '±')) { + [$number, $unit] = self::splitIntoValueAndUnit(ltrim($value, " ±")) ?? [ltrim($value, ' ±'), $unit]; + if (is_numeric($number)) { + return new self(name: $name, value_min: -abs((float) $number), value_max: abs((float) $number), unit: $unit, symbol: $symbol, group: $group); + } } - return new self($name, value_text: $value, unit: $unit, symbol: $symbol, group: $group); + //If no unit was passed to us, try to extract it from the value + if (empty($unit)) { + [$value, $unit] = self::splitIntoValueAndUnit($value) ?? [$value, null]; + } + + //Were we successful in trying to reduce the value to a number? + if ($value_text !== null && is_numeric($value)) { + return new self($name, value_text: $value_text, value_typ: (float) $value, unit: $unit, symbol: $symbol, + group: $group); + } + + return new self($name, value_text: $value.$value_text, unit: $unit, symbol: $symbol, group: $group); } /** @@ -87,14 +134,32 @@ class ParameterDTO { //Try to extract unit from value $unit = null; - if (is_string($value) && preg_match('/^(?[0-9.]+)\s*(?[°a-zA-Z_]+\s?\w{0,4})$/u', $value, $matches)) { - $value = $matches['value']; - $unit = $matches['unit']; + if (is_string($value)) { + [$number, $unit] = self::splitIntoValueAndUnit($value) ?? [$value, null]; - return self::parseValueField(name: $name, value: $value, unit: $unit, symbol: $symbol, group: $group); + return self::parseValueField(name: $name, value: $number, unit: $unit, symbol: $symbol, group: $group); } //Otherwise we assume that no unit is given return self::parseValueField(name: $name, value: $value, unit: null, symbol: $symbol, group: $group); } -} \ No newline at end of file + + /** + * Splits the given value into a value and a unit part if possible. + * If the value is not in the expected format, null is returned. + * @param string $value The value to split + * @return array|null An array with the value and the unit part or null if the value is not in the expected format + * @phpstan-return array{0: string, 1: string}|null + */ + public static function splitIntoValueAndUnit(string $value): ?array + { + if (preg_match('/^(?-?[0-9\.]+)\s*(?[%Ωµ°℃a-z_\/]+\s?\w{0,4})$/iu', $value, $matches)) { + $value = $matches['value']; + $unit = $matches['unit']; + + return [$value, $unit]; + } + + return null; + } +} diff --git a/src/Services/InfoProviderSystem/DTOs/PartDetailDTO.php b/src/Services/InfoProviderSystem/DTOs/PartDetailDTO.php index 9f365f1e..41d50510 100644 --- a/src/Services/InfoProviderSystem/DTOs/PartDetailDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/PartDetailDTO.php @@ -70,4 +70,4 @@ class PartDetailDTO extends SearchResultDTO footprint: $footprint, ); } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/DTOs/PriceDTO.php b/src/Services/InfoProviderSystem/DTOs/PriceDTO.php index 8c563149..2acf3e57 100644 --- a/src/Services/InfoProviderSystem/DTOs/PriceDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/PriceDTO.php @@ -28,19 +28,21 @@ use Brick\Math\BigDecimal; /** * 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 ?float $price_related_quantity = 1.0, ) { $this->price_as_big_decimal = BigDecimal::of($this->price); @@ -54,4 +56,4 @@ class PriceDTO { return $this->price_as_big_decimal; } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php b/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php index 6073cc5f..9ac142ff 100644 --- a/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php @@ -25,16 +25,17 @@ namespace App\Services\InfoProviderSystem\DTOs; /** * This DTO represents a purchase information for a part (supplier name, order number and prices). + * @see \App\Tests\Services\InfoProviderSystem\DTOs\PurchaseInfoDTOTest */ -class PurchaseInfoDTO +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 @@ -44,4 +45,4 @@ class PurchaseInfoDTO } } } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php b/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php index 355041bf..a70b2486 100644 --- a/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php @@ -27,9 +27,15 @@ use App\Entity\Parts\ManufacturingStatus; /** * This DTO represents a search result for a part. + * @see \App\Tests\Services\InfoProviderSystem\DTOs\SearchResultDTOTest */ class SearchResultDTO { + /** @var string|null An URL to a preview image */ + public readonly ?string $preview_image_url; + /** @var FileDTO|null The preview image as FileDTO object */ + public readonly ?FileDTO $preview_image_file; + public function __construct( /** @var string The provider key (e.g. "digikey") */ public readonly string $provider_key, @@ -46,14 +52,66 @@ class SearchResultDTO /** @var string|null The manufacturer part number */ public readonly ?string $mpn = null, /** @var string|null An URL to a preview image */ - public readonly ?string $preview_image_url = null, + ?string $preview_image_url = null, /** @var ManufacturingStatus|null The manufacturing status of the part */ public readonly ?ManufacturingStatus $manufacturing_status = null, /** @var string|null A link to the part on the providers page */ 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 + $this->preview_image_file = new FileDTO($preview_image_url); + $this->preview_image_url = $this->preview_image_file->url; + } else { + $this->preview_image_file = null; + $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 881d5f20..a655a0df 100644 --- a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php +++ b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php @@ -26,8 +26,8 @@ namespace App\Services\InfoProviderSystem; use App\Entity\Attachments\AttachmentType; use App\Entity\Attachments\PartAttachment; use App\Entity\Base\AbstractStructuralDBElement; -use App\Entity\Parameters\AbstractParameter; use App\Entity\Parameters\PartParameter; +use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\InfoProviderReference; use App\Entity\Parts\Manufacturer; @@ -37,24 +37,29 @@ use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Pricedetail; +use App\Repository\Parts\CategoryRepository; 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 Brick\Math\BigDecimal; +use App\Settings\SystemSettings\LocalizationSettings; use Doctrine\ORM\EntityManagerInterface; /** * This class converts DTOs to entities which can be persisted in the DB + * @see \App\Tests\Services\InfoProviderSystem\DTOtoEntityConverterTest */ final class DTOtoEntityConverter { 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; } /** @@ -87,6 +92,7 @@ final class DTOtoEntityConverter { $entity->setMinDiscountQuantity($dto->minimum_discount_amount); $entity->setPrice($dto->getPriceAsBigDecimal()); + $entity->setPriceRelatedQuantity($dto->price_related_quantity); //Currency TODO if ($dto->currency_iso_code !== null) { @@ -95,7 +101,6 @@ final class DTOtoEntityConverter $entity->setCurrency(null); } - return $entity; } @@ -129,7 +134,7 @@ final class DTOtoEntityConverter $entity->setAttachmentType($type); //If no name is given, try to extract the name from the URL - if (empty($dto->name)) { + if ($dto->name === null || $dto->name === '' || $dto->name === '0') { $entity->setName($this->getAttachmentNameFromURL($dto->url)); } else { $entity->setName($dto->name); @@ -157,6 +162,12 @@ final class DTOtoEntityConverter $entity->setMass($dto->mass); + //Try to map the category to an existing entity (but never create a new one) + if ($dto->category) { + //@phpstan-ignore-next-line For some reason php does not recognize the repo returns a category + $entity->setCategory($this->em->getRepository(Category::class)->findForInfoProvider($dto->category)); + } + $entity->setManufacturer($this->getOrCreateEntity(Manufacturer::class, $dto->manufacturer)); $entity->setFootprint($this->getOrCreateEntity(Footprint::class, $dto->footprint)); @@ -167,9 +178,21 @@ final class DTOtoEntityConverter //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 @@ -185,21 +208,39 @@ final class DTOtoEntityConverter $entity->setMasterPictureAttachment($preview_image); } + $attachments_grouped = []; + //Add other images - foreach ($dto->images ?? [] as $image) { + $images = $this->files_unique($dto->images ?? []); + foreach ($images as $image) { //Ensure that the image is not the same as the preview image if ($image->url === $dto->preview_image_url) { 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(); - foreach ($dto->datasheets ?? [] as $datasheet) { - $entity->addAttachment($this->convertFile($datasheet, $datasheet_type)); + $datasheets = $this->files_unique($dto->datasheets ?? []); + foreach ($datasheets as $datasheet) { + $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 @@ -210,6 +251,27 @@ final class DTOtoEntityConverter return $entity; } + /** + * Returns the given array of files with all duplicates removed. + * @param FileDTO[] $files + * @return FileDTO[] + */ + private function files_unique(array $files): array + { + $unique = []; + //We use the URL and name as unique identifier. If two file DTO have the same URL and name, they are considered equal + //and get filtered out, if it already exists in the array + foreach ($files as $file) { + //Skip already existing files, to preserve the order. The second condition ensure that we keep the version with a name over the one without a name + if (isset($unique[$file->url]) && $unique[$file->url]->name !== null) { + continue; + } + $unique[$file->url] = $file; + } + + return array_values($unique); + } + /** * Get the existing entity of the given class with the given name or create it if it does not exist. * If the name is null, null is returned. @@ -289,10 +351,10 @@ final class DTOtoEntityConverter //If the entity was newly created, set the file filter if ($tmp->getID() === null) { $tmp->setFiletypeFilter('image/*'); - $tmp->setAlternativeNames(self::TYPE_DATASHEETS_NAME); + $tmp->setAlternativeNames(self::TYPE_IMAGE_NAME); } return $tmp; } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/ExistingPartFinder.php b/src/Services/InfoProviderSystem/ExistingPartFinder.php new file mode 100644 index 00000000..614ca105 --- /dev/null +++ b/src/Services/InfoProviderSystem/ExistingPartFinder.php @@ -0,0 +1,79 @@ +findAllExisting($dto); + return count($results) > 0 ? $results[0] : null; + } + + /** + * Returns all existing local parts that match the search result. + * If no part is found, return an empty array. + * @param SearchResultDTO $dto + * @return Part[] + */ + public function findAllExisting(SearchResultDTO $dto): array + { + $qb = $this->em->getRepository(Part::class)->createQueryBuilder('part'); + $qb->select('part') + ->leftJoin('part.manufacturer', 'manufacturer') + ->Orwhere($qb->expr()->andX( + 'part.providerReference.provider_key = :providerKey', + 'part.providerReference.provider_id = :providerId', + )) + + //Or the manufacturer (allowing for alternative names) and the MPN (or part name) must match + ->OrWhere( + $qb->expr()->andX( + $qb->expr()->orX( + "ILIKE(manufacturer.name, :manufacturerName) = TRUE", + "ILIKE(manufacturer.alternative_names, :manufacturerAltNames) = TRUE", + ), + $qb->expr()->orX( + "ILIKE(part.manufacturer_product_number, :mpn) = TRUE", + "ILIKE(part.name, :mpn) = TRUE", + ) + ) + ) + ; + + $qb->setParameter('providerKey', $dto->provider_key); + $qb->setParameter('providerId', $dto->provider_id); + + $qb->setParameter('manufacturerName', $dto->manufacturer); + $qb->setParameter('manufacturerAltNames', '%'.$dto->manufacturer.'%'); + $qb->setParameter('mpn', $dto->mpn); + + return $qb->getQuery()->getResult(); + } +} diff --git a/src/Services/InfoProviderSystem/PartInfoRetriever.php b/src/Services/InfoProviderSystem/PartInfoRetriever.php index 1a31b197..0eb74642 100644 --- a/src/Services/InfoProviderSystem/PartInfoRetriever.php +++ b/src/Services/InfoProviderSystem/PartInfoRetriever.php @@ -27,6 +27,7 @@ use App\Entity\Parts\Part; 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 @@ final class PartInfoRetriever $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 @@ final class PartInfoRetriever $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 @@ final class PartInfoRetriever { $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/ProviderRegistry.php b/src/Services/InfoProviderSystem/ProviderRegistry.php index acfbdc1e..f6c398d2 100644 --- a/src/Services/InfoProviderSystem/ProviderRegistry.php +++ b/src/Services/InfoProviderSystem/ProviderRegistry.php @@ -27,6 +27,7 @@ use App\Services\InfoProviderSystem\Providers\InfoProviderInterface; /** * This class keeps track of all registered info providers and allows to find them by their key + * @see \App\Tests\Services\InfoProviderSystem\ProviderRegistryTest */ final class ProviderRegistry { diff --git a/src/Services/InfoProviderSystem/Providers/BatchInfoProviderInterface.php b/src/Services/InfoProviderSystem/Providers/BatchInfoProviderInterface.php new file mode 100644 index 00000000..549f117a --- /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 31b9cb41..423b5244 100644 --- a/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php +++ b/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace App\Services\InfoProviderSystem\Providers; use App\Entity\Parts\ManufacturingStatus; +use App\Exceptions\OAuthReconnectRequiredException; use App\Services\InfoProviderSystem\DTOs\FileDTO; use App\Services\InfoProviderSystem\DTOs\ParameterDTO; use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; @@ -31,6 +32,7 @@ use App\Services\InfoProviderSystem\DTOs\PriceDTO; 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 @@ -45,19 +47,26 @@ class DigikeyProvider implements InfoProviderInterface private readonly HttpClientInterface $digikeyClient; + /** + * A list of parameter IDs, that are always assumed as text only and will never be converted to a numerical value. + * This allows to fix issues like #682, where the "Supplier Device Package" was parsed as a numerical value. + */ + private const TEXT_ONLY_PARAMETERS = [ + 1291, //Supplier Device Package + 39246, //Package / Case + ]; public function __construct(HttpClientInterface $httpClient, private readonly OAuthTokenManager $authTokenManager, - private readonly string $currency, private readonly string $clientId, - 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, ] ]); @@ -70,7 +79,8 @@ class DigikeyProvider implements InfoProviderInterface '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, ]; } @@ -93,41 +103,57 @@ class DigikeyProvider implements InfoProviderInterface public function isActive(): bool { //The client ID has to be set and a token has to be available (user clicked connect) - return !empty($this->clientId) && $this->authTokenManager->hasToken(self::OAUTH_APP_NAME); + return $this->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; @@ -135,63 +161,88 @@ class DigikeyProvider implements InfoProviderInterface 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' => ManufacturingStatus::EOL, - '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; } /** @@ -208,10 +259,19 @@ class DigikeyProvider implements InfoProviderInterface foreach ($parameters as $parameter) { if ($parameter['ParameterId'] === 1291) { //Meaning "Manufacturer given footprint" - $footprint_name = $parameter['Value']; + $footprint_name = $parameter['ValueText']; } - $results[] = ParameterDTO::parseValueIncludingUnit($parameter['Parameter'], $parameter['Value']); + 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['ParameterText'], value_text: $parameter['ValueText']); + } else { //Otherwise try to parse it as a numerical value + $results[] = ParameterDTO::parseValueIncludingUnit($parameter['ParameterText'], $parameter['ValueText']); + } } return $results; @@ -229,7 +289,7 @@ class DigikeyProvider implements InfoProviderInterface $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 [ @@ -238,16 +298,22 @@ class DigikeyProvider implements InfoProviderInterface } /** - * @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']) { @@ -266,4 +332,4 @@ class DigikeyProvider implements InfoProviderInterface ]; } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/Providers/Element14Provider.php b/src/Services/InfoProviderSystem/Providers/Element14Provider.php index 7cc6693b..27dfb908 100644 --- a/src/Services/InfoProviderSystem/Providers/Element14Provider.php +++ b/src/Services/InfoProviderSystem/Providers/Element14Provider.php @@ -24,20 +24,20 @@ declare(strict_types=1); namespace App\Services\InfoProviderSystem\Providers; use App\Entity\Parts\ManufacturingStatus; -use App\Form\InfoProviderSystem\ProviderSelectType; 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\SearchResultDTO; 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'; @@ -45,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 @@ -56,7 +66,8 @@ class Element14Provider implements InfoProviderInterface '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, ]; } @@ -67,7 +78,7 @@ class Element14Provider implements InfoProviderInterface public function isActive(): bool { - return !empty($this->api_key); + return $this->settings->apiKey !== null && trim($this->settings->apiKey) !== ''; } /** @@ -79,13 +90,13 @@ class Element14Provider implements InfoProviderInterface $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, ], ]); @@ -109,23 +120,20 @@ class Element14Provider implements InfoProviderInterface 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 mixed[]|null $datasheets + * @param array|null $datasheets * @return FileDTO[]|null Array of FileDTOs */ private function parseDataSheets(?array $datasheets): ?array @@ -154,7 +162,7 @@ class Element14Provider implements InfoProviderInterface $locale = 'en_US'; } - return 'https://' . $this->store_id . '/productimages/standard/' . $locale . $image['baseName']; + return 'https://' . $this->settings->storeId . '/productimages/standard/' . $locale . $image['baseName']; } /** @@ -163,7 +171,7 @@ class Element14Provider implements InfoProviderInterface * @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 = []; @@ -181,7 +189,7 @@ class Element14Provider implements InfoProviderInterface distributor_name: self::DISTRIBUTOR_NAME, order_number: $sku, prices: $price_dtos, - product_url: $this->generateProductURL($sku) + product_url: $product_url ) ]; } @@ -189,42 +197,21 @@ class Element14Provider implements InfoProviderInterface public function getUsedCurrency(): string { //Decide based on the shop ID - return match ($this->store_id) { - 'bg.farnell.com' => 'EUR', + 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', - 'at.farnell.com' => 'EUR', 'ch.farnell.com' => 'CHF', - 'de.farnell.com' => 'EUR', - 'cpc.farnell.com' => 'GBP', - 'cpcireland.farnell.com' => 'EUR', - 'export.farnell.com' => 'GBP', - 'onecall.farnell.com' => 'GBP', - 'ie.farnell.com' => 'EUR', - 'il.farnell.com' => 'USD', - 'uk.farnell.com' => 'GBP', - 'es.farnell.com' => 'EUR', - 'ee.farnell.com' => 'EUR', - 'fi.farnell.com' => 'EUR', - 'fr.farnell.com' => 'EUR', + 'cpc.farnell.com', 'uk.farnell.com', 'onecall.farnell.com', 'export.farnell.com' => 'GBP', + 'il.farnell.com', 'www.newark.com' => 'USD', 'hu.farnell.com' => 'HUF', - 'it.farnell.com' => 'EUR', - 'lt.farnell.com' => 'EUR', - 'lv.farnell.com' => 'EUR', - 'be.farnell.com' => 'EUR', - 'nl.farnell.com' => 'EUR', 'no.farnell.com' => 'NOK', 'pl.farnell.com' => 'PLN', - 'pt.farnell.com' => 'EUR', - 'ro.farnell.com' => 'EUR', 'ru.farnell.com' => 'RUB', - 'sk.farnell.com' => 'EUR', - 'si.farnell.com' => 'EUR', 'se.farnell.com' => 'SEK', 'tr.farnell.com' => 'TRY', 'canada.newark.com' => 'CAD', 'mexico.newark.com' => 'MXN', - 'www.newark.com' => 'USD', 'cn.element14.com' => 'CNY', 'au.element14.com' => 'AUD', 'nz.element14.com' => 'NZD', @@ -237,7 +224,7 @@ class Element14Provider implements InfoProviderInterface '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) }; } @@ -322,4 +309,4 @@ class Element14Provider implements InfoProviderInterface 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 00000000..e0de9772 --- /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 30821bad..1f787559 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 @@ interface InfoProviderInterface * @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 new file mode 100755 index 00000000..ede34eb8 --- /dev/null +++ b/src/Services/InfoProviderSystem/Providers/LCSCProvider.php @@ -0,0 +1,455 @@ +. + */ + +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\Settings\InfoProviderSystem\LCSCSettings; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +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 LCSCSettings $settings) + { + + } + + public function getProviderInfo(): array + { + return [ + 'name' => 'LCSC', + 'description' => 'This provider uses the (unofficial) LCSC API to search for parts.', + 'url' => 'https://www.lcsc.com/', + 'disabled_help' => 'Enable this provider in the provider settings.', + 'settings_class' => LCSCSettings::class, + ]; + } + + public function getProviderKey(): string + { + return 'lcsc'; + } + + // This provider is always active + public function isActive(): bool + { + 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, bool $lightweight = false): PartDetailDTO + { + $response = $this->lcscClient->request('GET', self::ENDPOINT_URL . "/product/detail", [ + 'headers' => [ + 'Cookie' => new Cookie('currencyCode', $this->settings->currency) + ], + 'query' => [ + 'productCode' => $id, + ], + ]); + + $arr = $response->toArray(); + $product = $arr['result'] ?? null; + + if ($product === null) { + throw new \RuntimeException('Could not find product code: ' . $id); + } + + return $this->getPartDetail($product, $lightweight); + } + + /** + * @param string $url + * @return String + */ + 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; + } + } + return $url; + } + + /** + * @param string $term + * @param bool $lightweight If true, skip expensive operations like datasheet resolution + * @return PartDetailDTO[] + */ + private function queryByTerm(string $term, bool $lightweight = false): array + { + // Optimize: If term looks like an LCSC part number (starts with C followed by digits), + // use direct detail query instead of slower search + if (preg_match('/^C\d+$/i', trim($term))) { + try { + return [$this->queryDetail(trim($term), $lightweight)]; + } catch (\Exception $e) { + // If direct lookup fails, fall back to search + // This handles cases where the C-code might not exist + } + } + + $response = $this->lcscClient->request('POST', self::ENDPOINT_URL . "/search/v2/global", [ + 'headers' => [ + 'Cookie' => new Cookie('currencyCode', $this->settings->currency) + ], + 'json' => [ + 'keyword' => $term, + ], + ]); + + $arr = $response->toArray(); + + // Get products list + $products = $arr['result']['productSearchResultVO']['productList'] ?? []; + // Get product tip + $tipProductCode = $arr['result']['tipProductDetailUrlVO']['productCode'] ?? null; + + $result = []; + + // LCSC does not display LCSC codes in the search, instead taking you directly to the + // detailed product listing. It does so utilizing a product tip field. + // If product tip exists and there are no products in the product list try a detail query + if (count($products) === 0 && $tipProductCode !== null) { + $result[] = $this->queryDetail($tipProductCode, $lightweight); + } + + foreach ($products as $product) { + $result[] = $this->getPartDetail($product, $lightweight); + } + + return $result; + } + + /** + * Sanitizes a field by removing any HTML tags and other unwanted characters + * @param string|null $field + * @return string|null + */ + 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); + } + + + /** + * Takes a deserialized json object of the product and returns a PartDetailDTO + * @param array $product + * @return PartDetailDTO + */ + private function getPartDetail(array $product, bool $lightweight = false): PartDetailDTO + { + // Get product images in advance + $product_images = $this->getProductImages($product['productImages'] ?? null); + $product['productImageUrl'] ??= null; + + // If the product does not have a product image but otherwise has attached images, use the first one. + if (count($product_images) > 0) { + $product['productImageUrl'] ??= $product_images[0]->url; + } + + // LCSC puts HTML in footprints and descriptions sometimes randomly + $footprint = $product["encapStandard"] ?? null; + //If the footprint just consists of a dash, we'll assume it's empty + if ($footprint === '-') { + $footprint = null; + } + + //Build category by concatenating the catalogName and parentCatalogName + $category = $product['parentCatalogName'] ?? null; + if (isset($product['catalogName'])) { + $category = ($category ?? '') . ' -> ' . $product['catalogName']; + } + + return new PartDetailDTO( + provider_key: $this->getProviderKey(), + provider_id: $product['productCode'], + name: $product['productModel'], + description: $this->sanitizeField($product['productIntroEn']), + category: $this->sanitizeField($category ?? null), + manufacturer: $this->sanitizeField($product['brandNameEn'] ?? null), + mpn: $this->sanitizeField($product['productModel'] ?? null), + preview_image_url: $product['productImageUrl'], + manufacturing_status: null, + provider_url: $this->getProductShortURL($product['productCode']), + footprint: $this->sanitizeField($footprint), + datasheets: $lightweight ? [] : $this->getProductDatasheets($product['pdfUrl'] ?? null), + images: $product_images, // Always include images - users need to see them + parameters: $lightweight ? [] : $this->attributesToParameters($product['paramVOList'] ?? []), + vendor_infos: $lightweight ? [] : $this->pricesToVendorInfo($product['productCode'], $this->getProductShortURL($product['productCode']), $product['productPriceList'] ?? []), + mass: $product['weight'] ?? null, + ); + } + + /** + * Converts the price array to a VendorInfoDTO array to be used in the PartDetailDTO + * @param string $sku + * @param string $url + * @param array $prices + * @return array + */ + private function pricesToVendorInfo(string $sku, string $url, array $prices): array + { + $price_dtos = []; + + foreach ($prices as $price) { + $price_dtos[] = new PriceDTO( + minimum_discount_amount: $price['ladder'], + price: $price['productPrice'], + currency_iso_code: $this->getUsedCurrency($price['currencySymbol']), + includes_tax: false, + ); + } + + return [ + new PurchaseInfoDTO( + distributor_name: self::DISTRIBUTOR_NAME, + order_number: $sku, + prices: $price_dtos, + product_url: $url, + ) + ]; + } + + /** + * Converts LCSC currency symbol to an ISO code. + * @param string $currency + * @return string + */ + private function getUsedCurrency(string $currency): string + { + //Decide based on the currency symbol + return match ($currency) { + 'US$', '$' => 'USD', + '€' => 'EUR', + 'A$' => 'AUD', + 'C$' => 'CAD', + '£' => 'GBP', + 'HK$' => 'HKD', + 'JP¥' => 'JPY', + 'RM' => 'MYR', + 'S$' => 'SGD', + '₽' => 'RUB', + 'kr' => 'SEK', + 'kr.' => 'DKK', + '₹' => 'INR', + //Fallback to the configured currency + default => $this->settings->currency, + }; + } + + /** + * Returns a valid LCSC product short URL from product code + * @param string $product_code + * @return string + */ + private function getProductShortURL(string $product_code): string + { + return 'https://www.lcsc.com/product-detail/' . $product_code . '.html'; + } + + /** + * Returns a product datasheet FileDTO array from a single pdf url + * @param string $url + * @return FileDTO[] + */ + private function getProductDatasheets(?string $url): array + { + if ($url === null) { + return []; + } + + $realUrl = $this->getRealDatasheetUrl($url); + + return [new FileDTO($realUrl, null)]; + } + + /** + * Returns a FileDTO array with a list of product images + * @param array|null $images + * @return FileDTO[] + */ + private function getProductImages(?array $images): array + { + return array_map(static fn($image) => new FileDTO($image), $images ?? []); + } + + /** + * @param array|null $attributes + * @return ParameterDTO[] + */ + private function attributesToParameters(?array $attributes): array + { + $result = []; + + foreach ($attributes as $attribute) { + + //Skip this attribute if it's empty + if (in_array(trim((string) $attribute['paramValueEn']), ['', '-'], true)) { + continue; + } + + $result[] = ParameterDTO::parseValueIncludingUnit(name: $attribute['paramNameEn'], value: $attribute['paramValueEn'], group: null); + } + + return $result; + } + + public function searchByKeyword(string $keyword): array + { + return $this->queryByTerm($keyword, true); // Use lightweight mode for search + } + + /** + * Batch search multiple keywords asynchronously (like JavaScript Promise.all) + * @param array $keywords Array of keywords to search + * @return array Results indexed by keyword + */ + public function searchByKeywordsBatch(array $keywords): array + { + if (empty($keywords)) { + return []; + } + + $responses = []; + $results = []; + + // Start all requests immediately (like JavaScript promises without await) + foreach ($keywords as $keyword) { + if (preg_match('/^C\d+$/i', trim($keyword))) { + // Direct detail API call for C-codes + $responses[$keyword] = $this->lcscClient->request('GET', self::ENDPOINT_URL . "/product/detail", [ + 'headers' => [ + 'Cookie' => new Cookie('currencyCode', $this->settings->currency) + ], + 'query' => [ + 'productCode' => trim($keyword), + ], + ]); + } else { + // Search API call for other terms + $responses[$keyword] = $this->lcscClient->request('POST', self::ENDPOINT_URL . "/search/v2/global", [ + 'headers' => [ + 'Cookie' => new Cookie('currencyCode', $this->settings->currency) + ], + 'json' => [ + 'keyword' => $keyword, + ], + ]); + } + } + + // Now collect all results (like .then() in JavaScript) + foreach ($responses as $keyword => $response) { + try { + $arr = $response->toArray(); // This waits for the response + $results[$keyword] = $this->processSearchResponse($arr, $keyword); + } catch (\Exception $e) { + $results[$keyword] = []; // Empty results on error + } + } + + return $results; + } + + private function processSearchResponse(array $arr, string $keyword): array + { + $result = []; + + // Check if this looks like a detail response (direct C-code lookup) + if (isset($arr['result']['productCode'])) { + $product = $arr['result']; + $result[] = $this->getPartDetail($product, true); // lightweight mode + } else { + // This is a search response + $products = $arr['result']['productSearchResultVO']['productList'] ?? []; + $tipProductCode = $arr['result']['tipProductDetailUrlVO']['productCode'] ?? null; + + // If no products but has tip, we'd need another API call - skip for batch mode + foreach ($products as $product) { + $result[] = $this->getPartDetail($product, true); // lightweight mode + } + } + + return $result; + } + + public function getDetails(string $id): PartDetailDTO + { + $tmp = $this->queryByTerm($id, false); + 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); + } + + return $tmp[0]; + } + + public function getCapabilities(): array + { + return [ + ProviderCapabilities::BASIC, + ProviderCapabilities::PICTURE, + ProviderCapabilities::DATASHEET, + ProviderCapabilities::PRICE, + ProviderCapabilities::FOOTPRINT, + ]; + } +} diff --git a/src/Services/InfoProviderSystem/Providers/MouserProvider.php b/src/Services/InfoProviderSystem/Providers/MouserProvider.php new file mode 100644 index 00000000..3171c994 --- /dev/null +++ b/src/Services/InfoProviderSystem/Providers/MouserProvider.php @@ -0,0 +1,379 @@ +. + */ + +/* +* This file provide an interface with the Mouser API V2 (also compatible with the V1) +* +* Copyright (C) 2023 Pasquale D'Orsi (https://github.com/pdo59) +* +* TODO: Obtain an API keys with an US Mouser user (currency $) and test the result of prices +* +*/ + +declare(strict_types=1); + + +namespace App\Services\InfoProviderSystem\Providers; + +use App\Entity\Parts\ManufacturingStatus; +use App\Services\InfoProviderSystem\DTOs\FileDTO; +use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; +use App\Services\InfoProviderSystem\DTOs\PriceDTO; +use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; +use App\Settings\InfoProviderSystem\MouserSettings; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + + +class MouserProvider implements InfoProviderInterface +{ + + private const ENDPOINT_URL = 'https://api.mouser.com/api/v2/search'; + + public const DISTRIBUTOR_NAME = 'Mouser'; + + public function __construct( + private readonly HttpClientInterface $mouserClient, + private readonly MouserSettings $settings, + ) { + } + + public function getProviderInfo(): array + { + return [ + '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 settings to enable.', + 'settings_class' => MouserSettings::class + ]; + } + + public function getProviderKey(): string + { + return 'mouser'; + } + + public function isActive(): bool + { + return $this->settings->apiKey !== '' && $this->settings->apiKey !== null; + } + + public function searchByKeyword(string $keyword): array + { + /* + SearchByKeywordRequest description: + Search parts by keyword and return a maximum of 50 parts. + + keyword* string + Used for keyword part search. + + records integer($int32) + Used to specify how many records the method should return. + + startingRecord integer($int32) + Indicates where in the total recordset the return set should begin. + 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. + Refers to options supported by the search engine. + Only one value at a time is supported. + Available options: None | Rohs | InStock | RohsAndInStock - can use string representations or integer IDs: 1[None] | 2[Rohs] | 4[InStock] | 8[RohsAndInStock]. + + searchWithYourSignUpLanguage string + Optional. + If not provided, the default is false. + Used when searching for keywords in the language specified when you signed up for Search API. + Can use string representation: true. + { + "SearchByKeywordRequest": { + "keyword": "BC557", + "records": 0, + "startingRecord": 0, + "searchOptions": "", + "searchWithYourSignUpLanguage": "" + } + } + */ + + $response = $this->mouserClient->request('POST', self::ENDPOINT_URL."/keyword", [ + 'query' => [ + 'apiKey' => $this->settings->apiKey + ], + 'json' => [ + 'SearchByKeywordRequest' => [ + 'keyword' => $keyword, + 'records' => $this->settings->searchLimit, //self::NUMBER_OF_RESULTS, + 'startingRecord' => 0, + '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); + } + + public function getDetails(string $id): PartDetailDTO + { + /* + SearchByPartRequest description: + Search parts by part number and return a maximum of 50 parts. + + mouserPartNumber string + Used to search parts by the specific Mouser part number with a maximum input of 10 part numbers, separated by a pipe symbol for the search. + Each part number must be a minimum of 3 characters and a maximum of 40 characters. For example: 494-JANTX2N2222A|610-2N2222-TL|637-2N2222A + + partSearchOptions string + Optional. + If not provided, the default is None. Refers to options supported by the search engine. Only one value at a time is supported. + The following values are valid: None | Exact - can use string representations or integer IDs: 1[None] | 2[Exact] + + { + "SearchByPartRequest": { + "mouserPartNumber": "string", + "partSearchOptions": "string" + } + } + */ + + $response = $this->mouserClient->request('POST', self::ENDPOINT_URL."/partnumber", [ + 'query' => [ + 'apiKey' => $this->settings->apiKey, + ], + 'json' => [ + 'SearchByPartRequest' => [ + 'mouserPartNumber' => $id, + 'partSearchOptions' => 2 + ] + ], + ]); + + // 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 + if (count($tmp) === 0) { + 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); + } + + return reset($tmp); + } + + public function getCapabilities(): array + { + return [ + ProviderCapabilities::BASIC, + ProviderCapabilities::PICTURE, + ProviderCapabilities::DATASHEET, + ProviderCapabilities::PRICE, + ]; + } + + + /** + * @param ResponseInterface $response + * @return PartDetailDTO[] + */ + private function responseToDTOArray(ResponseInterface $response): array + { + $arr = $response->toArray(); + + if (isset($arr['SearchResults'])) { + $products = $arr['SearchResults']['Parts'] ?? []; + } else { + throw new \RuntimeException('Unknown response format: ' .json_encode($arr, JSON_THROW_ON_ERROR)); + } + + $result = []; + foreach ($products as $product) { + + //Check if we have a valid product number. We assume that a product number, must have at least 4 characters + //Otherwise filter it out + if (strlen($product['MouserPartNumber']) < 4) { + continue; + } + + //Check if we have a mass field available + $mass = null; + if (isset($product['UnitWeightKg']['UnitWeight'])) { + $mass = (float) $product['UnitWeightKg']['UnitWeight']; + //The mass is given in kg, we want it in g + $mass *= 1000; + } + + + $result[] = new PartDetailDTO( + provider_key: $this->getProviderKey(), + provider_id: $product['MouserPartNumber'], + name: $product['ManufacturerPartNumber'], + description: $product['Description'], + category: $product['Category'], + manufacturer: $product['Manufacturer'], + mpn: $product['ManufacturerPartNumber'], + preview_image_url: $product['ImagePath'], + manufacturing_status: $this->releaseStatusCodeToManufacturingStatus( + $product['LifecycleStatus'] ?? null, + (int) ($product['AvailabilityInStock'] ?? 0) + ), + provider_url: $product['ProductDetailUrl'], + datasheets: $this->parseDataSheets($product['DataSheetUrl'] ?? null, + $product['MouserPartNumber'] ?? null), + vendor_infos: $this->pricingToDTOs($product['PriceBreaks'] ?? [], $product['MouserPartNumber'], + $product['ProductDetailUrl']), + mass: $mass, + ); + } + return $result; + } + + + private function parseDataSheets(?string $sheetUrl, ?string $sheetName): ?array + { + if ($sheetUrl === null || $sheetUrl === '' || $sheetUrl === '0') { + return null; + } + $result = []; + $result[] = new FileDTO(url: $sheetUrl, name: $sheetName); + return $result; + } + + /* + * Mouser API price is a string in the form "n[.,]nnn[.,] currency" + * then this convert it to a number + * Austria has a format like "€ 2,10" + */ + private function priceStrToFloat($val): float + { + //Remove any character that is not a number, dot or comma (like currency symbols) + $val = preg_replace('/[^0-9.,]/', '', $val); + + //Trim the string + $val = trim($val); + + //Convert commas to dots + $val = str_replace(",", ".", $val); + //Remove any dot that is not the last one (to avoid problems with thousands separators) + $val = preg_replace('/\.(?=.*\.)/', '', $val); + 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 + * @param string $order_number + * @param string $product_url + * @return PurchaseInfoDTO[] + */ + private function pricingToDTOs(array $price_breaks, string $order_number, string $product_url): array + { + $prices = []; + + foreach ($price_breaks as $price_break) { + $number = $this->priceStrToFloat($price_break['Price']); + $prices[] = new PriceDTO( + minimum_discount_amount: $price_break['Quantity'], + price: (string)$number, + currency_iso_code: $this->mapCurrencyCode($price_break['Currency']) + ); + } + + return [ + new PurchaseInfoDTO(distributor_name: self::DISTRIBUTOR_NAME, order_number: $order_number, prices: $prices, + product_url: $product_url) + ]; + } + + + /* Converts the product status from the MOUSER API to the manufacturing status used in Part-DB: + Factory Special Order - Ordine speciale in fabbrica + Not Recommended for New Designs - Non raccomandato per nuovi progetti + New Product - Nuovo prodotto + End of Life - Fine vita + -vuoto- - Attivo + + TODO: Probably need to review the values of field Lifecyclestatus + */ + /** + * Converts the lifecycle status from the Mouser API to a ManufacturingStatus + * @param string|null $productStatus The lifecycle status from the Mouser API + * @param int $availableInStock The number of parts available in stock + * @return ManufacturingStatus|null + */ + private function releaseStatusCodeToManufacturingStatus(?string $productStatus, int $availableInStock = 0): ?ManufacturingStatus + { + $tmp = match ($productStatus) { + null => null, + "New Product" => ManufacturingStatus::ANNOUNCED, + "Not Recommended for New Designs" => ManufacturingStatus::NRFND, + "Factory Special Order", "Obsolete" => ManufacturingStatus::DISCONTINUED, + "End of Life" => ManufacturingStatus::EOL, + default => ManufacturingStatus::ACTIVE, + }; + + //If the part would be assumed to be announced, check if it is in stock, then it is active + if ($tmp === ManufacturingStatus::ANNOUNCED && $availableInStock > 0) { + $tmp = ManufacturingStatus::ACTIVE; + } + + return $tmp; + } +} diff --git a/src/Services/InfoProviderSystem/Providers/OEMSecretsProvider.php b/src/Services/InfoProviderSystem/Providers/OEMSecretsProvider.php new file mode 100644 index 00000000..b705e04a --- /dev/null +++ b/src/Services/InfoProviderSystem/Providers/OEMSecretsProvider.php @@ -0,0 +1,1469 @@ +. + */ + +/** + * OEMSecretsProvider Class + * + * This class is responsible for interfacing with the OEMSecrets API (version 3.0.1) to retrieve and manage information + * about electronic components. Since the API does not provide a unique identifier for each part, the class aggregates + * results based on "part_number" and "manufacturer_id". It also transforms unstructured descriptions into structured + * parameters and aggregates datasheets and images provided by multiple distributors. + * The OEMSecrets API returns results by matching the provided part number not only with the original part number + * but also with the distributor-assigned part number and/or the part description. + * + * Key functionalities: + * - Aggregation of results based on part_number and manufacturer_id to ensure unique identification of parts. + * - Conversion of component descriptions into structured parameters (ParameterDTO) for better clarity and searchability. + * - Aggregation of datasheets and images from multiple distributors, ensuring that all available resources are collected. + * - Price handling, including filtering of distributors that offer zero prices, controlled by the `zero_price` configuration variable. + * - A sorting algorithm that first prioritizes exact matches with the keyword, followed by alphabetical sorting of items + * with the same prefix (e.g., "BC546", "BC546A", "BC546B"), and finally, sorts by either manufacturer or completeness + * based on the specified criteria. + * - Sorting the distributors: + * 1. Environment's country_code first. + * 2. Region matching environment's country_code, prioritizing "Global" ('XX'). + * 3. Distributors with null country_code/region are placed last. + * 4. Final fallback is alphabetical sorting by region and country_code. + * + * Configuration: + * - The ZERO_PRICE variable must be set in the `.env.local` file. If is set to 0, the class will skip distributors + * that do not offer valid prices for the components. + * - Currency and country settings can also be specified for localized pricing and distributor filtering. + * - Generation of parameters: if SET_PARAM is set to 1 the parameters for the part are generated from the description + * transforming unstructured descriptions into structured parameters; each parameter in description should have the form: + * "...;name1:value1;name2:value2" + * - Sorting is guided by SORT_CRITERIA variable. The sorting process first arranges items based on the provided keyword. + * Then, if set to 'C', it further sorts by completeness (prioritizing items with the most detailed information). + * If set to 'M', it further sorts by manufacturer name. If unset or set to any other value, no sorting is performed. + * Distributors within each item are further sorted based on country_code and region, following the rules explained + * in the previous comment. + * + * Data Handling: + * - The class divides and stores component information across multiple session arrays: + * - `basic_info_results`: Stores basic information like name, description, manufacturer, and category. + * - `datasheets_results`: Aggregates datasheets provided by distributors, ensuring no duplicates. + * - `images_results`: Collects images of components from various sources, preventing duplication. + * - `parameters_results`: Extracts and stores key parameters parsed from component descriptions. + * - `purchase_info_results`: Contains detailed purchasing information like pricing and distributor details. + * + * - By splitting the data into separate session arrays, the class optimizes memory usage and simplifies retrieval + * of specific details without loading the entire dataset at once. + * + * Technical Details: + * - Uses OEMSecrets API (version 3.0.1) to retrieve component data. + * - Data processing includes sanitizing input, avoiding duplicates, and dynamically adjusting information as new distributor + * data becomes available (e.g., adding missing datasheets or parameters from subsequent API responses). + * + * @package App\Services\InfoProviderSystem\Providers + * @author Pasquale D'Orsi (https://github.com/pdo59) + * @version 1.2.0 + * @since 2024 August + */ + + +declare(strict_types=1); + +namespace App\Services\InfoProviderSystem\Providers; + +use App\Entity\Parts\ManufacturingStatus; +use App\Services\InfoProviderSystem\DTOs\FileDTO; +use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; +use App\Services\InfoProviderSystem\DTOs\PriceDTO; +use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; +use App\Services\InfoProviderSystem\DTOs\ParameterDTO; +use App\Settings\InfoProviderSystem\OEMSecretsSettings; +use App\Settings\InfoProviderSystem\OEMSecretsSortMode; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Psr\Cache\CacheItemPoolInterface; + + +class OEMSecretsProvider implements InfoProviderInterface +{ + + private const ENDPOINT_URL = 'https://oemsecretsapi.com/partsearch'; + + public function __construct( + private readonly HttpClientInterface $oemsecretsClient, + private readonly OEMSecretsSettings $settings, + private readonly CacheItemPoolInterface $partInfoCache + ) + { + } + + private array $countryNameToCodeMap = [ + 'Andorra' => 'AD', + 'United Arab Emirates' => 'AE', + 'Antarctica' => 'AQ', + 'Argentina' => 'AR', + 'Austria' => 'AT', + 'Australia' => 'AU', + 'Belgium' => 'BE', + 'Bolivia' => 'BO', + 'Brazil' => 'BR', + 'Bouvet Island' => 'BV', + 'Belarus' => 'BY', + 'Canada' => 'CA', + 'Switzerland' => 'CH', + 'Chile' => 'CL', + 'China' => 'CN', + 'Colombia' => 'CO', + 'Czech Republic' => 'CZ', + 'Germany' => 'DE', + 'Denmark' => 'DK', + 'Ecuador' => 'EC', + 'Estonia' => 'EE', + 'Western Sahara' => 'EH', + 'Spain' => 'ES', + 'Finland' => 'FI', + 'Falkland Islands' => 'FK', + 'Faroe Islands' => 'FO', + 'France' => 'FR', + 'United Kingdom' => 'GB', + 'Georgia' => 'GE', + 'French Guiana' => 'GF', + 'Guernsey' => 'GG', + 'Gibraltar' => 'GI', + 'Greenland' => 'GL', + 'Greece' => 'GR', + 'South Georgia and the South Sandwich Islands' => 'GS', + 'Guyana' => 'GY', + 'Hong Kong' => 'HK', + 'Heard Island and McDonald Islands' => 'HM', + 'Croatia' => 'HR', + 'Hungary' => 'HU', + 'Ireland' => 'IE', + 'Isle of Man' => 'IM', + 'India' => 'IN', + 'Iceland' => 'IS', + 'Italy' => 'IT', + 'Jamaica' => 'JM', + 'Japan' => 'JP', + 'North Korea' => 'KP', + 'South Korea' => 'KR', + 'Kazakhstan' => 'KZ', + 'Liechtenstein' => 'LI', + 'Sri Lanka' => 'LK', + 'Lithuania' => 'LT', + 'Luxembourg' => 'LU', + 'Monaco' => 'MC', + 'Moldova' => 'MD', + 'Montenegro' => 'ME', + 'North Macedonia' => 'MK', + 'Malta' => 'MT', + 'Netherlands' => 'NL', + 'Norway' => 'NO', + 'New Zealand' => 'NZ', + 'Peru' => 'PE', + 'Philippines' => 'PH', + 'Poland' => 'PL', + 'Portugal' => 'PT', + 'Paraguay' => 'PY', + 'Romania' => 'RO', + 'Serbia' => 'RS', + 'Russia' => 'RU', + 'Solomon Islands' => 'SB', + 'Sudan' => 'SD', + 'Sweden' => 'SE', + 'Singapore' => 'SG', + 'Slovenia' => 'SI', + 'Svalbard and Jan Mayen' => 'SJ', + 'Slovakia' => 'SK', + 'San Marino' => 'SM', + 'Somalia' => 'SO', + 'Suriname' => 'SR', + 'Syria' => 'SY', + 'Eswatini' => 'SZ', + 'Turks and Caicos Islands' => 'TC', + 'French Southern Territories' => 'TF', + 'Togo' => 'TG', + 'Thailand' => 'TH', + 'Tajikistan' => 'TJ', + 'Tokelau' => 'TK', + 'Turkmenistan' => 'TM', + 'Tunisia' => 'TN', + 'Tonga' => 'TO', + 'Turkey' => 'TR', + 'Trinidad and Tobago' => 'TT', + 'Tuvalu' => 'TV', + 'Taiwan' => 'TW', + 'Tanzania' => 'TZ', + 'Ukraine' => 'UA', + 'Uganda' => 'UG', + 'United States Minor Outlying Islands' => 'UM', + 'United States' => 'US', + 'Uruguay' => 'UY', + 'Uzbekistan' => 'UZ', + 'Vatican City' => 'VA', + 'Venezuela' => 'VE', + 'British Virgin Islands' => 'VG', + 'U.S. Virgin Islands' => 'VI', + 'Vietnam' => 'VN', + 'Vanuatu' => 'VU', + 'Wallis and Futuna' => 'WF', + 'Yemen' => 'YE', + 'South Africa' => 'ZA', + 'Zambia' => 'ZM', + 'Zimbabwe' => 'ZW', + 'Global' => 'XX' + ]; + + private array $distributorCountryCodes = []; + private array $countryCodeToRegionMap = []; + + /** + * Get information about this provider + * + * @return array An associative array with the following keys (? means optional): + * - name: The (user friendly) name of the provider (e.g. "Digikey"), will be translated + * - description?: A short description of the provider (e.g. "Digikey is a ..."), will be translated + * - logo?: The logo of the provider (e.g. "digikey.png") + * - url?: The url of the provider (e.g. "https://www.digikey.com") + * - disabled_help?: A help text which is shown when the provider is disabled, explaining how to enable it + * - oauth_app_name?: The name of the OAuth app which is used for authentication (e.g. "ip_digikey_oauth"). If this is set a connect button will be shown + * + * @phpstan-return array{ name: string, description?: string, logo?: string, url?: string, disabled_help?: string, oauth_app_name?: string } + */ + public function getProviderInfo(): array + { + return [ + 'name' => 'OEMSecrets', + 'description' => 'This provider uses the OEMSecrets API to search for parts.', + 'url' => 'https://www.oemsecrets.com/', + 'disabled_help' => 'Configure the API key in the provider settings to enable.', + 'settings_class' => OEMSecretsSettings::class + ]; + } + /** + * Returns a unique key for this provider, which will be saved into the database + * and used to identify the provider + * @return string A unique key for this provider (e.g. "digikey") + */ + public function getProviderKey(): string + { + return 'oemsecrets'; + } + + /** + * Checks if this provider is enabled or not (meaning that it can be used for searching) + * @return bool True if the provider is enabled, false otherwise + */ + public function isActive(): bool + { + return $this->settings->apiKey !== null && $this->settings->apiKey !== ''; + } + + + /** + * Searches for products based on a given keyword using the OEMsecrets Part Search API. + * + * This method queries the OEMsecrets API to retrieve distributor data for the provided part number, + * including details such as pricing, compliance, and inventory. It supports both direct API queries + * and debugging with local JSON files. The results are processed, cached, and then sorted based + * on the keyword and specified criteria. + * + * @param string $keyword The part number to search for + * @return array An array of processed product details, sorted by relevance and additional criteria. + * + * @throws \Exception If the JSON file used for debugging is not found or contains errors. + */ + public function searchByKeyword(string $keyword): array + { + /* + oemsecrets Part Search API 3.0.1 + + "https://oemsecretsapi.com/partsearch? + searchTerm=BC547 + &apiKey=icawpb0bspoo2c6s64uv4vpdfp2vgr7e27bxw0yct2bzh87mpl027x353uelpq2x + ¤cy=EUR + &countryCode=IT" + + partsearch description: + Use the Part Search API to find distributor data for a full or partial manufacturer + part number including part details, pricing, compliance and inventory. + + Required Parameter Format Description + searchTerm string Part number you are searching for + apiKey string Your unique API key provided to you by OEMsecrets + + Additional Parameter Format Description + countryCode string The country you want to output for + currency string / array The currency you want the prices to be displayed as + + To display the output for GB and to view prices in USD, add [ countryCode=GB ] and [ currency=USD ] + as seen below: + oemsecretsapi.com/partsearch?apiKey=abcexampleapikey123&searchTerm=bd04&countryCode=GB¤cy=USD + + To view prices in both USD and GBP add [ currency[]=USD¤cy[]=GBP ] + oemsecretsapi.com/partsearch?searchTerm=bd04&apiKey=abcexampleapikey123¤cy[]=USD¤cy[]=GBP + + */ + + + // Activate this block when querying the real APIs + //------------------ + + $response = $this->oemsecretsClient->request('GET', self::ENDPOINT_URL, [ + 'query' => [ + 'searchTerm' => $keyword, + 'apiKey' => $this->settings->apiKey, + 'currency' => $this->settings->currency, + 'countryCode' => $this->settings->country, + ], + ]); + + $response_array = $response->toArray(); + //------------------*/ + + // Or activate this block when we use json file for debugging + /*/------------------ + $jsonFilePath = ''; + if (!file_exists($jsonFilePath)) { + throw new \Exception("JSON file not found."); + } + $jsonContent = file_get_contents($jsonFilePath); + $response_array = json_decode($jsonContent, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \Exception("JSON file decode failed: " . json_last_error_msg()); + } + //------------------*/ + + $products = $response_array['stock'] ?? []; + + $results = []; + $basicInfoResults = []; + $datasheetsResults = []; + $imagesResults = []; + $parametersResults = []; + $purchaseInfoResults = []; + + foreach ($products as $product) { + if (!isset($product['part_number'], $product['manufacturer'])) { + continue; // Skip invalid product entries + } + $provider_id = $this->generateProviderId($product['part_number'], $product['manufacturer']); + + $partDetailDTO = $this->processBatch( + $product, + $provider_id, + $basicInfoResults, + $datasheetsResults, + $imagesResults, + $parametersResults, + $purchaseInfoResults + ); + + if ($partDetailDTO !== null) { + $results[$provider_id] = $partDetailDTO; + $cacheKey = $this->getCacheKey($provider_id); + $cacheItem = $this->partInfoCache->getItem($cacheKey); + $cacheItem->set($partDetailDTO); + $cacheItem->expiresAfter(3600 * 24); + $this->partInfoCache->save($cacheItem); + } + } + + //Force garbage collection to free up memory + gc_collect_cycles(); + + // Sort of the results + $this->sortResultsData($results, $keyword); + + //Force garbage collection to free up memory + gc_collect_cycles(); + + return $results; + + } + + /** + * Generates a cache key for storing part details based on the provided provider ID. + * + * This method creates a unique cache key by prefixing the provider ID with 'part_details_' + * and hashing the provider ID using MD5 to ensure a consistent and compact key format. + * + * @param string $provider_id The unique identifier of the provider or part. + * @return string The generated cache key. + */ + private function getCacheKey(string $provider_id): string { + return 'oemsecrets_part_' . md5($provider_id); + } + + + /** + * Retrieves detailed information about the part with the given provider ID from the cache. + * + * This method checks the cache for the details of the specified part. If the details are + * found in the cache, they are returned. If not, an exception is thrown indicating that + * the details could not be found. + * + * @param string $id The unique identifier of the provider or part. + * @return PartDetailDTO The detailed information about the part. + * + * @throws \Exception If no details are found for the given provider ID. + */ + public function getDetails(string $id): PartDetailDTO + { + $cacheKey = $this->getCacheKey($id); + $cacheItem = $this->partInfoCache->getItem($cacheKey); + + if ($cacheItem->isHit()) { + return $cacheItem->get(); + } + //If we have no cached result yet, we extract the part number (first part of our ID) and search for it + $partNumber = explode('|', $id)[0]; + + //The searchByKeyword method will write the results to cache, so we can just try it again afterwards + $this->searchByKeyword($partNumber); + + $cacheItem = $this->partInfoCache->getItem($cacheKey); + if ($cacheItem->isHit()) { + return $cacheItem->get(); + } + + // If the details still are not found in the cache, throw an exception + throw new \RuntimeException("Details not found for provider_id $id"); + } + + + /** + * A list of capabilities this provider supports (which kind of data it can provide). + * Not every part have to contain all of these data, but the provider should be able to provide them in general. + * Currently, this list is purely informational and not used in functional checks. + * @return ProviderCapabilities[] + */ + public function getCapabilities(): array + { + return [ + ProviderCapabilities::BASIC, + ProviderCapabilities::PICTURE, + ProviderCapabilities::DATASHEET, + ProviderCapabilities::PRICE, + ]; + } + + + /** + * Processes a single product and updates arrays for basic information, datasheets, images, parameters, + * and purchase information. Aggregates and organizes data received for a specific `part_number` and `manufacturer_id`. + * Distributors within the product are also sorted based on country_code and region. + * + * @param array $product The product data received from the OEMSecrets API. + * @param string $provider_id A string that contains the unique key created for the part + * @param array &$basicInfoResults Array containing the basic product information (e.g., name, description, category). + * @param array &$datasheetsResults Array containing datasheets collected from various distributors for the product. + * @param array &$imagesResults Array containing images of the product collected from various distributors. + * @param array &$parametersResults Array containing technical parameters extracted from the product descriptions. + * @param array &$purchaseInfoResults Array containing purchase information, including distributors and pricing details. + * + * @return PartDetailDTO|null Returns a PartDetailDTO object if the product is processed successfully, otherwise null. + * + * @throws \Exception If a required key in the product data is missing or if there is an issue creating the DTO. + * + * @see createOrUpdateBasicInfo() Creates or updates the basic product information. + * @see getPrices() Extracts the pricing information for the product. + * @see parseDataSheets() Parses and prevents duplication of datasheets. + * @see getImages() Extracts and avoids duplication of images. + * @see getParameters() Extracts technical parameters from the product description. + * @see createPurchaseInfoDTO() Creates a PurchaseInfoDTO containing distributor and price information. + * + * @note Distributors within the product are sorted by country_code and region: + * 1. Distributors with the environment's country_code come first. + * 2. Distributors in the same region as the environment's country_code are next, + * with "Global" ('XX') prioritized within this region. + * 3. Distributors with null country_code or region are placed last. + * 4. Remaining distributors are sorted alphabetically by region and country_code. + + */ + private function processBatch( + array $product, + string $provider_id, + array &$basicInfoResults, + array &$datasheetsResults, + array &$imagesResults, + array &$parametersResults, + array &$purchaseInfoResults + ): ?PartDetailDTO + { + if (!isset($product['manufacturer'], $product['part_number'])) { + throw new \InvalidArgumentException("Missing required product data: 'manufacturer' or 'part_number'"); + } + + // Retrieve the country_code associated with the distributor and store it in the $distributorCountryCodes array. + $distributorCountry = $product['distributor']['distributor_country'] ?? null; + $distributorName = $product['distributor']['distributor_name'] ?? null; + $distributorRegion = $product['distributor']['distributor_region'] ?? null; + + if ($distributorCountry && $distributorName) { + $countryCode = $this->mapCountryNameToCode($distributorCountry); + if ($countryCode) { + $this->distributorCountryCodes[$distributorName] = $countryCode; + } + if ($distributorRegion) { + $this->countryCodeToRegionMap[$countryCode] = $distributorRegion; + } + } + + // Truncate the description and handle notes + $thenotes = ''; + $description = $product['description'] ?? ''; + if (strlen($description) > 100) { + $thenotes = $description; // Save the complete description + $description = substr($description, 0, 100) . '...'; // Truncate the description + } + + // Extract prices + $priceDTOs = $this->getPrices($product); + if (empty($priceDTOs) && !$this->settings->keepZeroPrices) { + return null; // Skip products without valid prices + } + + $existingBasicInfo = isset($basicInfoResults[$provider_id]) && is_array($basicInfoResults[$provider_id]) + ? $basicInfoResults[$provider_id] + : []; + + $basicInfoResults[$provider_id] = $this->createOrUpdateBasicInfo( + $provider_id, + $product, + $description, + $thenotes, + $existingBasicInfo + ); + + // Update images, datasheets, and parameters + + $newDatasheets = $this->parseDataSheets($product['datasheet_url'] ?? null, null, $datasheetsResults[$provider_id] ?? []); + if ($newDatasheets !== null) { + $datasheetsResults[$provider_id] = array_merge($datasheetsResults[$provider_id] ?? [], $newDatasheets); + } + + $imagesResults[$provider_id] = $this->getImages($product, $imagesResults[$provider_id] ?? []); + if ($this->settings->parseParams) { + $parametersResults[$provider_id] = $this->getParameters($product, $parametersResults[$provider_id] ?? []); + } else { + $parametersResults[$provider_id] = []; + } + + // Handle purchase information + $currentDistributor = $this->createPurchaseInfoDTO($product, $priceDTOs, $purchaseInfoResults[$provider_id] ?? []); + if ($currentDistributor !== null) { + $purchaseInfoResults[$provider_id][] = $currentDistributor; + } + + // If there is data in $purchaseInfoResults, sort it before creating the PartDetailDTO + if (!empty($purchaseInfoResults[$provider_id])) { + usort($purchaseInfoResults[$provider_id], function ($a, $b) { + $nameA = $a->distributor_name; + $nameB = $b->distributor_name; + + $countryCodeA = $this->distributorCountryCodes[$nameA] ?? null; + $countryCodeB = $this->distributorCountryCodes[$nameB] ?? null; + + $regionA = $this->countryCodeToRegionMap[$countryCodeA] ?? ''; + $regionB = $this->countryCodeToRegionMap[$countryCodeB] ?? ''; + + // If the map is empty or doesn't contain the key for $this->country_code, assign a placeholder region. + $regionForEnvCountry = $this->countryCodeToRegionMap[$this->settings->country] ?? ''; + + // Convert to string before comparison to avoid mixed types + $countryCodeA = (string) $countryCodeA; + $countryCodeB = (string) $countryCodeB; + $regionA = (string) $regionA; + $regionB = (string) $regionB; + + + // Step 0: If either country code is null, place it at the end + if ($countryCodeA === '' || $regionA === '') { + return 1; // Metti A dopo B + } elseif ($countryCodeB === '' || $regionB === '') { + return -1; // Metti B dopo A + } + + // Step 1: country_code from the environment + if ($countryCodeA === $this->settings->country && $countryCodeB !== $this->settings->country) { + return -1; + } elseif ($countryCodeA !== $this->settings->country && $countryCodeB === $this->settings->country) { + return 1; + } + + // Step 2: Sort by environment's region, prioritizing "Global" (XX) + if ($regionA === $regionForEnvCountry && $regionB !== $regionForEnvCountry) { + return -1; + } elseif ($regionA !== $regionForEnvCountry && $regionB === $regionForEnvCountry) { + return 1; + } + + // Step 3: If regions are the same, prioritize "Global" (XX) + if ($regionA === $regionB) { + if ($countryCodeA === 'XX' && $countryCodeB !== 'XX') { + return -1; + } elseif ($countryCodeA !== 'XX' && $countryCodeB === 'XX') { + return 1; + } + } + + // Step 4: Alphabetical sorting by region and country_code + $regionComparison = strcasecmp($regionA , $regionB); + if ($regionComparison !== 0) { + return $regionComparison; + } + + // Alphabetical sorting as a fallback + return strcasecmp($countryCodeA, $countryCodeB); + }); + } + // Convert the gathered data into a PartDetailDTO + + $partDetailDTO = new PartDetailDTO( + provider_key: $basicInfoResults[$provider_id]['provider_key'], + provider_id: $provider_id, + name: $basicInfoResults[$provider_id]['name'], + description: $basicInfoResults[$provider_id]['description'], + category: $basicInfoResults[$provider_id]['category'], + manufacturer: $basicInfoResults[$provider_id]['manufacturer'], + mpn: $basicInfoResults[$provider_id]['mpn'], + preview_image_url: $basicInfoResults[$provider_id]['preview_image_url'], + manufacturing_status: $basicInfoResults[$provider_id]['manufacturing_status'], + provider_url: $basicInfoResults[$provider_id]['provider_url'], + footprint: $basicInfoResults[$provider_id]['footprint'] ?? null, + notes: $basicInfoResults[$provider_id]['notes'] ?? null, + datasheets: $datasheetsResults[$provider_id] ?? [], + images: $imagesResults[$provider_id] ?? [], + parameters: $parametersResults[$provider_id] ?? [], + vendor_infos: $purchaseInfoResults[$provider_id] ?? [] + ); + + return $partDetailDTO; + } + + + /** + * Extracts pricing information from the product data, converts it to PriceDTO objects, + * and returns them as an array. + * + * @param array{ + * prices?: array>, + * source_currency?: string + * } $product The product data from the OEMSecrets API containing price details. + * + * @return PriceDTO[] Array of PriceDTO objects representing different price tiers for the product. + */ + private function getPrices(array $product): array + { + $prices = $product['prices'] ?? []; + $sourceCurrency = $product['source_currency'] ?? null; + $priceDTOs = []; + + // Flag to check if we have added prices in the preferred currency + $foundPreferredCurrency = false; + + if (is_array($prices)) { + // Step 1: Check if prices exist in the preferred currency + if (isset($prices[$this->settings->currency]) && is_array($prices[$this->settings->currency])) { + $priceDetails = $prices[$this->$this->settings->currency]; + foreach ($priceDetails as $priceDetail) { + if ( + is_array($priceDetail) && + isset($priceDetail['unit_break'], $priceDetail['unit_price']) && + is_numeric($priceDetail['unit_break']) && + is_string($priceDetail['unit_price']) && + $priceDetail['unit_price'] !== "0.0000" + ) { + $priceDTOs[] = new PriceDTO( + minimum_discount_amount: (float)$priceDetail['unit_break'], + price: (string)$priceDetail['unit_price'], + currency_iso_code: $this->settings->currency, + includes_tax: false, + price_related_quantity: 1.0 + ); + $foundPreferredCurrency = true; + } + } + } + + // Step 2: If no prices in the preferred currency, use source currency + if (!$foundPreferredCurrency && $sourceCurrency && isset($prices[$sourceCurrency]) && is_array($prices[$sourceCurrency])) { + $priceDetails = $prices[$sourceCurrency]; + foreach ($priceDetails as $priceDetail) { + if ( + is_array($priceDetail) && + isset($priceDetail['unit_break'], $priceDetail['unit_price']) && + is_numeric($priceDetail['unit_break']) && + is_string($priceDetail['unit_price']) && + $priceDetail['unit_price'] !== "0.0000" + ) { + $priceDTOs[] = new PriceDTO( + minimum_discount_amount: (float)$priceDetail['unit_break'], + price: (string)$priceDetail['unit_price'], + currency_iso_code: $sourceCurrency, + includes_tax: false, + price_related_quantity: 1.0 + ); + } + } + } + } + + return $priceDTOs; + } + + + /** + * Retrieves product images provided by the distributor. Prevents duplicates based on the image name. + * @param array{ + * image_url?: string + * } $product The product data from the OEMSecrets API containing image URLs. + * @param FileDTO[] $existingImages Optional. Existing images for the product to avoid duplicates. + * + * @return FileDTO[] Array of FileDTO objects representing the product images. + */ + private function getImages(array $product, array $existingImages = []): array + { + $images = $existingImages; + $imageUrl = $product['image_url'] ?? null; + + if ($imageUrl) { + $imageName = basename(parse_url($imageUrl, PHP_URL_PATH)); + if (!in_array($imageName, array_column($images, 'name'), true)) { + $images[] = new FileDTO(url: $imageUrl, name: $imageName); + } + } + return $images; + } + + /** + * Extracts technical parameters from the product description, ensures no duplicates, and returns them as an array. + * + * @param array{ + * description?: string + * } $product The product data from the OEMSecrets API containing product descriptions. + * @param ParameterDTO[] $existingParameters Optional. Existing parameters for the product to avoid duplicates. + * + * @return ParameterDTO[] Array of ParameterDTO objects representing technical parameters extracted from the product description. + */ + private function getParameters(array $product, array $existingParameters = []): array + { + $parameters = $existingParameters; + $description = $product['description'] ?? ''; + + // Logic to extract parameters from the description + $extractedParameters = $this->parseDescriptionToParameters($description) ?? []; + + foreach ($extractedParameters as $newParam) { + $isDuplicate = false; + foreach ($parameters as $existingParam) { + if ($existingParam->name === $newParam->name) { + $isDuplicate = true; + break; + } + } + if (!$isDuplicate) { + $parameters[] = $newParam; + } + } + + return $parameters; + } + + /** + * Creates a PurchaseInfoDTO object containing distributor and pricing information for a product. + * Ensures that the distributor name is valid and prices are available. + * + * @param array{ + * distributor?: array{ + * distributor_name?: string + * }, + * sku?: string, + * source_part_number: string, + * buy_now_url?: string, + * lead_time_weeks?: mixed + * } $product The product data from the OEMSecrets API. + * @param PriceDTO[] $priceDTOs Array of PriceDTO objects representing pricing tiers. + * @param PurchaseInfoDTO[] $existingPurchaseInfos Optional. Existing purchase information for the product to avoid duplicates. + * + * @return PurchaseInfoDTO|null A PurchaseInfoDTO object containing the distributor information, or null if invalid. + */ + private function createPurchaseInfoDTO(array $product, array $priceDTOs, array $existingPurchaseInfos = []): ?PurchaseInfoDTO + { + $distributor_name = $product['distributor']['distributor_name'] ?? null; + if ($distributor_name && !empty($priceDTOs)) { + $sku = isset($product['sku']) ? (string)$product['sku'] : null; + $order_number_base = $sku ?: (string)$product['source_part_number']; + $order_number = $order_number_base; + + // Remove duplicates from the quantity/price tiers + $uniquePriceDTOs = []; + foreach ($priceDTOs as $priceDTO) { + $key = $priceDTO->minimum_discount_amount . '-' . $priceDTO->price; + $uniquePriceDTOs[$key] = $priceDTO; + } + $priceDTOs = array_values($uniquePriceDTOs); + + // Differentiate $order_number if duplicated + if ($this->isDuplicateOrderNumber($order_number, $distributor_name, $existingPurchaseInfos)) { + $lead_time_weeks = isset($product['lead_time_weeks']) ? (string)$product['lead_time_weeks'] : ''; + $order_number = $order_number_base . '-' . $lead_time_weeks; + + // If there is still a duplicate after adding lead_time_weeks + $counter = 1; + while ($this->isDuplicateOrderNumber($order_number, $distributor_name, $existingPurchaseInfos)) { + $order_number = $order_number_base . '-' . $lead_time_weeks . '-' . $counter; + $counter++; + } + } + + return new PurchaseInfoDTO( + distributor_name: $distributor_name, + order_number: $order_number, + prices: $priceDTOs, + product_url: $this->unwrapURL($product['buy_now_url'] ?? null) + ); + } + return null; // Return null if no valid distributor exists + } + + /** + * Checks if an order number already exists for a given distributor in the existing purchase infos. + * + * @param string $order_number The order number to check. + * @param string $distributor_name The name of the distributor. + * @param PurchaseInfoDTO[] $existingPurchaseInfos The existing purchase information to check against. + * @return bool True if a duplicate order number is found, otherwise false. + */ + private function isDuplicateOrderNumber(string $order_number, string $distributor_name, array $existingPurchaseInfos): bool + { + foreach ($existingPurchaseInfos as $purchaseInfo) { + if ($purchaseInfo->distributor_name === $distributor_name && $purchaseInfo->order_number === $order_number) { + return true; + } + } + return false; + } + + /** + * Creates or updates the basic information of a product, including the description, category, manufacturer, + * and other metadata. This function manages the PartDetailDTO creation or update. + * + * @param string $provider_id The unique identifier for the product based on part_number and manufacturer. + * * @param array{ + * part_number: string, + * category: string, + * manufacturer: string, + * source_part_number: string, + * image_url?: string, + * life_cycle?: string, + * quantity_in_stock?: int + * } $product The product data from the OEMSecrets API. + * @param string $description The truncated description for the product. + * @param string $thenotes The full description saved as notes for the product. + * + * @return array The updated or newly created PartDetailDTO containing basic product information. + */ + private function createOrUpdateBasicInfo( + string $provider_id, + array $product, + string $description, + string $thenotes, + ?array $existingBasicInfo + ): array { + // If there is no existing basic info array, we create a new one + if (is_null($existingBasicInfo)) { + return [ + 'provider_key' => $this->getProviderKey(), + 'provider_id' => $provider_id, + 'name' => $product['part_number'], + 'description' => $description, + 'category' => $product['category'], + 'manufacturer' => $product['manufacturer'], + 'mpn' => $product['source_part_number'], + 'preview_image_url' => $product['image_url'] ?? null, + 'manufacturing_status' => $this->releaseStatusCodeToManufacturingStatus( + $product['life_cycle'] ?? null, + (int)($product['quantity_in_stock'] ?? 0) + ), + 'provider_url' => $this->generateInquiryUrl($product['part_number']), + 'notes' => $thenotes, + 'footprint' => null + ]; + } + + // Update fields only if empty or undefined, with additional check for preview_image_url + return [ + 'provider_key' => $existingBasicInfo['provider_key'] ?? $this->getProviderKey(), + 'provider_id' => $existingBasicInfo['provider_id'] ?? $provider_id, + 'name' => $existingBasicInfo['name'] ?? $product['part_number'], + // Update description if it's null/empty + 'description' => !empty($existingBasicInfo['description']) + ? $existingBasicInfo['description'] + : $description, + // Update category if it's null/empty + 'category' => !empty($existingBasicInfo['category']) + ? $existingBasicInfo['category'] + : $product['category'], + 'manufacturer' => $existingBasicInfo['manufacturer'] ?? $product['manufacturer'], + 'mpn' => $existingBasicInfo['mpn'] ?? $product['source_part_number'], + 'preview_image_url' => !empty($existingBasicInfo['preview_image_url']) + ? $existingBasicInfo['preview_image_url'] + : ($product['image_url'] ?? null), + 'manufacturing_status' => !empty($existingBasicInfo['manufacturing_status']) + ? $existingBasicInfo['manufacturing_status'] + : $this->releaseStatusCodeToManufacturingStatus( + $product['life_cycle'] ?? null, + (int)($product['quantity_in_stock'] ?? 0) + ), + 'provider_url' => $existingBasicInfo['provider_url'] ?? $this->generateInquiryUrl($product['part_number']), // ?? $product['buy_now_url'], + 'notes' => $existingBasicInfo['notes'] ?? $thenotes, + 'footprint' => null + ]; + } + + /** + * Parses the datasheet URL and returns an array of FileDTO objects representing the datasheets. + * If the datasheet name is not provided, it attempts to extract the file name from the URL. + * If multiple datasheets with the same default name are encountered, the function appends a + * numeric suffix to ensure uniqueness. + * The query parameter used to extract the event link can be customized. + * + * URL Requirements: + * - The URL should be a valid URL string. + * - The URL can include a query parameter named "event_link", which contains a sub-URL where the + * actual datasheet file name is located (e.g., a link to a PDF file). + * - If "event_link" is not present, the function attempts to extract the file name directly from + * the URL path. + * - The URL path should ideally end with a valid file extension (e.g., .pdf, .doc, .xls, etc.). + * + * Example 1: + * Given URL: `https://example.com/datasheet.php?event_link=https%3A%2F%2Ffiles.example.com%2Fdatasheet.pdf` + * Extracted name: `datasheet.pdf` + * + * Example 2: + * Given URL: `https://example.com/files/datasheet.pdf` + * Extracted name: `datasheet.pdf` + * + * Example 3 (default name fallback): + * Given URL: `https://example.com/files/noextensionfile` + * Extracted name: `datasheet.pdf` + * + * @param string|null $sheetUrl The URL of the datasheet. + * @param string|null $sheetName The optional name of the datasheet. If null, the name is extracted from the URL. + * @param array $existingDatasheets The array of existing datasheets to check for duplicates. + * + * @return FileDTO[]|null Returns an array containing the new datasheet if unique, or null if the datasheet is a duplicate or invalid. + * + * @see FileDTO Used to create datasheet objects with a URL and name. + */ + private function parseDataSheets(?string $sheetUrl, ?string $sheetName, array $existingDatasheets = []): ?array + { + if ($sheetUrl === null || $sheetUrl === '' || $sheetUrl === '0') { + return null; + } + + //Unwrap the URL (remove analytics part) + $sheetUrl = $this->unwrapURL($sheetUrl); + + // If the datasheet name is not provided, extract it from the URL + if ($sheetName === null) { + $urlPath = parse_url($sheetUrl, PHP_URL_PATH); + if ($urlPath === false) { + throw new \RuntimeException("Invalid URL path: $sheetUrl"); + } + + // If "event_link" does not exist, try to extract the name from the main URL path + $sheetName = basename($urlPath); + if (!str_contains($sheetName, '.') || !preg_match('/\.(pdf|doc|docx|xls|xlsx|ppt|pptx)$/i', $sheetName)) { + // If the name does not have a valid extension, assign a default name + $sheetName = 'datasheet_' . uniqid('', true) . '.pdf'; + } + } + + // Create an array of existing file names + $existingNames = array_map(static function ($existingDatasheet) { + return $existingDatasheet->name; + }, $existingDatasheets); + + // Check if the name already exists + if (in_array($sheetName, $existingNames, true)) { + // The name already exists, so do not add the datasheet + return null; + } + + // Create an array with the datasheet data if it does not already exist + $result = []; + $result[] = new FileDTO(url: $sheetUrl, name: $sheetName); + return $result; + } + + /** + * Converts the lifecycle status from the API to a ManufacturingStatus + * - "Factory Special Order" / "Ordine speciale in fabbrica" + * - "Not Recommended for New Designs" / "Non raccomandato per nuovi progetti" + * - "New Product" / "Nuovo prodotto" (if availableInStock > 0 else ANNOUNCED) + * - "End of Life" / "Fine vita" + * - vuoto / "Attivo" + * + * @param string|null $productStatus The lifecycle status from the Mouser API. Expected values are: + * - "Factory Special Order" + * - "Not Recommended for New Designs" + * - "New Product" + * - "End of Life" + * - "Obsolete" + * @param int $availableInStock The number of parts available in stock. + * @return ManufacturingStatus|null Returns the corresponding ManufacturingStatus or null if the status is unknown. + * + * @todo Probably need to review the values of field Lifecyclestatus. + */ + private function releaseStatusCodeToManufacturingStatus(?string $productStatus, int $availableInStock = 0): ?ManufacturingStatus + { + $tmp = match ($productStatus) { + null => null, + "New Product" => ManufacturingStatus::ANNOUNCED, + "Not Recommended for New Designs" => ManufacturingStatus::NRFND, + "Factory Special Order", "Obsolete" => ManufacturingStatus::DISCONTINUED, + "End of Life" => ManufacturingStatus::EOL, + default => null, //ManufacturingStatus::ACTIVE, + }; + + //If the part would be assumed to be announced, check if it is in stock, then it is active + if ($tmp === ManufacturingStatus::ANNOUNCED && $availableInStock > 0) { + $tmp = ManufacturingStatus::ACTIVE; + } + + return $tmp; + } + + /** + * Parses the given product description to extract parameters and convert them into `ParameterDTO` objects. + * If the description contains only a single `:`, it is considered unstructured and ignored. + * The function processes the description by searching for key-value pairs in the format `name: value`, + * ignoring any parts of the description that do not follow this format. Parameters are split using either + * `;` or `,` as separators. + * + * The extraction logic handles typical values, ranges, units, and textual information from the description. + * If the description is empty or cannot be processed into valid parameters, the function returns null. + * + * @param string|null $description The description text from which parameters are to be extracted. + * + * @return ParameterDTO[]|null Returns an array of `ParameterDTO` objects if parameters are successfully extracted, + * or null if no valid parameters can be extracted from the description. + */ + private function parseDescriptionToParameters(?string $description): ?array + { + // If the description is null or empty, return null + if ($description === null || trim($description) === '') { + return null; + } + + // If the description contains only a single ':', return null + if (substr_count($description, ':') === 1) { + return null; + } + + // Array to store parsed parameters + $parameters = []; + + // Split the description using the ';' separator + $parts = preg_split('/[;,]/', $description); //explode(';', $description); + + // Process each part of the description + foreach ($parts as $part) { + $part = trim($part); + + // Check if the part contains a key-value structure + if (str_contains($part, ':')) { + [$name, $value] = explode(':', $part, 2); + $name = trim($name); + $value = trim($value); + + // Attempt to parse the value, handling ranges, units, and additional information + $parsedValue = $this->customParseValueIncludingUnit($name, $value); + + // If the value was successfully parsed, create a ParameterDTO + if ($parsedValue) { + // Convert numeric values to float + $value_typ = isset($parsedValue['value_typ']) ? (float)$parsedValue['value_typ'] : null; + $value_min = isset($parsedValue['value_min']) ? (float)$parsedValue['value_min'] : null; + $value_max = isset($parsedValue['value_max']) ? (float)$parsedValue['value_max'] : null; + + $parameters[] = new ParameterDTO( + name: $parsedValue['name'], + value_text: $parsedValue['value_text'] ?? null, + value_typ: $value_typ, + value_min: $value_min, + value_max: $value_max, + unit: $parsedValue['unit'] ?? null, // Add extracted unit + symbol: $parsedValue['symbol'] ?? null // Add extracted symbol + ); + } + } + } + + return !empty($parameters) ? $parameters : null; + } + + /** + * Parses a value that may contain both a numerical value and its corresponding unit. + * This function splits the value into its numerical part and its unit, handling cases + * where the value includes symbols, ranges, or additional text. It also detects and + * processes plus/minus ranges, typical values, and other special formats. + * + * Example formats that can be handled: + * - "2.5V" + * - "±5%" + * - "1-10A" + * - "2.5 @text" + * - "~100 Ohm" + * + * @param string $value The value string to be parsed, which may contain a number, unit, or both. + * + * @return array An associative array with parsed components: + * - 'name' => string (the name of the parameter) + * - 'value_typ' => float|null (the typical or parsed value) + * - 'range_min' => float|null (the minimum value if it's a range) + * - 'range_max' => float|null (the maximum value if it's a range) + * - 'value_text' => string|null (any additional text or symbol) + * - 'unit' => string|null (the detected or default unit) + * - 'symbol' => string|null (any special symbol or additional text) + */ + private function customParseValueIncludingUnit(string $name, string $value): array + { + // Parse using logic for units, ranges, and other elements + $result = [ + 'name' => $name, + 'value_typ' => null, + 'value_min' => null, + 'value_max' => null, + 'value_text' => null, + 'unit' => null, + 'symbol' => null, + ]; + + // Trim any whitespace from the value + $value = trim($value); + + // Handle ranges and plus/minus signs + if (str_contains($value, '...') || str_contains($value, '~') || str_contains($value, '±')) { + // Handle ranges + $value = str_replace(['...', '~'], '...', $value); // Normalize range separators + $rangeParts = preg_split('/\s*[\.\~]\s*/', $value); + + if (count($rangeParts) === 2) { + // Splitting the values and units + $parsedMin = $this->customSplitIntoValueAndUnit($rangeParts[0]); + $parsedMax = $this->customSplitIntoValueAndUnit($rangeParts[1]); + + // Assigning the parsed values + $result['value_min'] = $parsedMin['value_typ']; + $result['value_max'] = $parsedMax['value_typ']; + + // Determine the unit + $result['unit'] = $parsedMax['unit'] ?? $parsedMin['unit']; + } + + } elseif (str_contains($value, '@')) { + // If we find "@", we treat it as additional textual information + [$numericValue, $textValue] = explode('@', $value); + $result['value_typ'] = (float) $numericValue; + $result['value_text'] = trim($textValue); + } else { + // Check if the value is numeric with a unit + if (preg_match('/^([\+\-]?\d+(\.\d+)?)([a-zA-Z%°]+)?$/u', $value, $matches)) { + // It is a number with or without a unit + $result['value_typ'] = (float) $matches[1]; + $result['unit'] = $matches[3] ?? null; + } else { + // It's not a number, so we treat it as text + $result['value_text'] = $value; + } + } + + return $result; + } + + /** + * Splits a string into a numerical value and its associated unit. The function attempts to separate + * a number from its unit, handling common formats where the unit follows the number (e.g., "50kHz", "10A"). + * The function assumes the unit is the non-numeric part of the string. + * + * Example formats that can be handled: + * - "100 Ohm" + * - "10 MHz" + * - "5kV" + * - "±5%" + * + * @param string $value1 The input string containing both a numerical value and a unit. + * @param string|null $value2 Optional. A second value string, typically used for ranges (e.g., "10-20A"). + * + * @return array An associative array with the following elements: + * - 'value_typ' => string|null The first numerical part of the string. + * - 'unit' => string|null The unit part of the string, or null if no unit is detected. + * - 'value_min' => string|null The minimum value in a range, if applicable. + * - 'value_max' => string|null The maximum value in a range, if applicable. + */ + private function customSplitIntoValueAndUnit(string $value1, ?string $value2 = null): array + { + // Separate numbers and units (basic parsing handling) + $unit = null; + $value_typ = null; + + // Search for the number + unit pattern + if (preg_match('/^([\+\-]?\d+(\.\d+)?)([a-zA-Z%°]+)?$/u', $value1, $matches)) { + $value_typ = $matches[1]; + $unit = $matches[3] ?? null; + } + + $result = [ + 'value_typ' => $value_typ, + 'unit' => $unit, + ]; + + if ($value2 !== null) { + if (preg_match('/^([\+\-]?\d+(\.\d+)?)([a-zA-Z%°]+)?$/u', $value2, $matches2)) { + $result['value_min'] = $value_typ; + $result['value_max'] = $matches2[1]; + $result['unit'] = $matches2[3] ?? $unit; // If both values have the same unit, we keep it + } + } + + return $result; + } + + /** + * Generates the API URL to fetch product information for the specified part number from OEMSecrets. + * Ensures that the base API URL and any query parameters are properly formatted. + * + * @param string $partNumber The part number to include in the URL. + * @param string $oemInquiry The inquiry path for the OEMSecrets API, with a default value of 'compare/'. + * This parameter represents the specific API endpoint to query. + * + * @return string The complete provider URL including the base provider URL, the inquiry path, and the part number. + * + * Example: + * If the base URL is "https://www.oemsecrets.com/", the inquiry path is "compare/", and the part number is "NE555", + * the resulting URL will be: "https://www.oemsecrets.com/compare/NE555" + */ + private function generateInquiryUrl(string $partNumber, string $oemInquiry = 'compare/'): string + { + $baseUrl = rtrim($this->getProviderInfo()['url'], '/') . '/'; + $inquiryPath = trim($oemInquiry, '/') . '/'; + $encodedPartNumber = urlencode(trim($partNumber)); + return $baseUrl . $inquiryPath . $encodedPartNumber; + } + + /** + * Sorts the results data array based on the specified search keyword and sorting criteria. + * The sorting process involves multiple phases: + * 1. Exact match with the search keyword. + * 2. Prefix match with the search keyword. + * 3. Alphabetical order of the suffix following the keyword. + * 4. Optional sorting by completeness or manufacturer based on the sort criteria. + * + * The sorting criteria (`sort_criteria`) is an environment variable configured in the `.env.local` file: + * PROVIDER_OEMSECRETS_SORT_CRITERIA + * It determines the final sorting phase: + * - 'C': Sort by completeness. + * - 'M': Sort by manufacturer. + * + * @param array $resultsData The array of result objects to be sorted. Each object should have 'name' and 'manufacturer' properties. + * @param string $searchKeyword The search keyword used for sorting the results. + * + * @return void + */ + private function sortResultsData(array &$resultsData, string $searchKeyword): void + { + // If the SORT_CRITERIA is not 'C' or 'M', do not sort + if ($this->settings->sortMode !== OEMSecretsSortMode::COMPLETENESS && $this->settings->sortMode !== OEMSecretsSortMode::MANUFACTURER) { + return; + } + usort($resultsData, function ($a, $b) use ($searchKeyword) { + $nameA = trim($a->name); + $nameB = trim($b->name); + + // First phase: Sorting by exact match with the keyword + $exactMatchA = strcasecmp($nameA, $searchKeyword) === 0; + $exactMatchB = strcasecmp($nameB, $searchKeyword) === 0; + + if ($exactMatchA && !$exactMatchB) { + return -1; + } elseif (!$exactMatchA && $exactMatchB) { + return 1; + } + + // Second phase: Sorting by prefix (name starting with the keyword) + $startsWithKeywordA = stripos($nameA, $searchKeyword) === 0; + $startsWithKeywordB = stripos($nameB, $searchKeyword) === 0; + + if ($startsWithKeywordA && !$startsWithKeywordB) { + return -1; + } elseif (!$startsWithKeywordA && $startsWithKeywordB) { + return 1; + } + + if ($startsWithKeywordA && $startsWithKeywordB) { + // Alphabetical sorting of suffixes + $suffixA = substr($nameA, strlen($searchKeyword)); + $suffixB = substr($nameB, strlen($searchKeyword)); + $suffixComparison = strcasecmp($suffixA, $suffixB); + + if ($suffixComparison !== 0) { + return $suffixComparison; + } + } + + // Final sorting: by completeness or manufacturer, if necessary + if ($this->settings->sortMode === OEMSecretsSortMode::COMPLETENESS) { + return $this->compareByCompleteness($a, $b); + } elseif ($this->settings->sortMode === OEMSecretsSortMode::MANUFACTURER) { + return strcasecmp($a->manufacturer, $b->manufacturer); + } + + }); + } + + /** + * Compares two objects based on their "completeness" score. + * The completeness score is calculated by the `calculateCompleteness` method, which assigns a numeric score + * based on the amount of information (such as parameters, datasheets, images, etc.) available for each object. + * The comparison is done in descending order, giving priority to the objects with higher completeness. + * + * @param object $a The first object to compare. + * @param object $b The second object to compare. + * + * @return int A negative value if $b is more complete than $a, zero if they are equally complete, + * or a positive value if $a is more complete than $b. + */ + private function compareByCompleteness(object $a, object $b): int + { + // Calculate the completeness score for each object + $completenessA = $this->calculateCompleteness($a); + $completenessB = $this->calculateCompleteness($b); + + // Sort in descending order by completeness (higher score is better) + return $completenessB - $completenessA; + } + + + /** + * Calculates a "completeness" score for a given part object based on the presence and count of various attributes. + * The completeness score is used to prioritize parts that have more detailed information. + * + * The score is calculated as follows: + * - Counts the number of elements in the `parameters`, `datasheets`, `images`, and `vendor_infos` arrays. + * - Adds 1 point for the presence of `category`, `description`, `mpn`, `preview_image_url`, and `footprint`. + * - Adds 1 or 2 points based on the presence or absence of `manufacturing_status` (higher score if `null`). + * + * @param object $part The part object for which the completeness score is calculated. The object is expected + * to have properties like `parameters`, `datasheets`, `images`, `vendor_infos`, `category`, + * `description`, `mpn`, `preview_image_url`, `footprint`, and `manufacturing_status`. + * + * @return int The calculated completeness score, with a higher score indicating more complete information. + */ + private function calculateCompleteness(object $part): int + { + // Counts the number of elements in each field that can have multiple values + $paramsCount = is_array($part->parameters) ? count($part->parameters) : 0; + $datasheetsCount = is_array($part->datasheets) ? count($part->datasheets) : 0; + $imagesCount = is_array($part->images) ? count($part->images) : 0; + $vendorInfosCount = is_array($part->vendor_infos) ? count($part->vendor_infos) : 0; + + // Check for the presence of single fields and assign a score + $categoryScore = !empty($part->category) ? 1 : 0; + $descriptionScore = !empty($part->description) ? 1 : 0; + $mpnScore = !empty($part->mpn) ? 1 : 0; + $previewImageScore = !empty($part->preview_image_url) ? 1 : 0; + $footprintScore = !empty($part->footprint) ? 1 : 0; + + // Weight for manufacturing_status: higher if null + $manufacturingStatusScore = is_null($part->manufacturing_status) ? 2 : 1; + + // Sum the counts and scores to obtain a completeness score + return $paramsCount + + $datasheetsCount + + $imagesCount + + $vendorInfosCount + + $categoryScore + + $descriptionScore + + $mpnScore + + $previewImageScore + + $footprintScore + + $manufacturingStatusScore; + } + + + /** + * Generates a unique provider ID by concatenating the part number and manufacturer name, + * separated by a pipe (`|`). The generated ID is typically used to uniquely identify + * a specific part from a particular manufacturer. + * + * @param string $partNumber The part number of the product. + * @param string $manufacturer The name of the manufacturer. + * + * @return string The generated provider ID, in the format "partNumber|manufacturer". + */ + private function generateProviderId(string $partNumber, string $manufacturer): string + { + return trim($partNumber) . '|' . trim($manufacturer); + } + + /** + * Maps the name of a country to its corresponding ISO 3166-1 alpha-2 code. + * + * @param string|null $countryName The name of the country to map. + * @return string|null The ISO code for the country, or null if not found. + */ + private function mapCountryNameToCode(?string $countryName): ?string + { + return $this->countryNameToCodeMap[$countryName] ?? null; + } + + /** + * Removes the analytics tracking parts from the URLs returned by the API. + * + * @param string|null $url + * @return string|null + */ + private function unwrapURL(?string $url): ?string + { + if ($url === null) { + return null; + } + + //Check if the URL is a one redirected via analytics + if (str_contains($url, 'analytics.oemsecrets.com/main.php')) { + //Extract the URL from the analytics URL + $queryParams = []; + parse_str(parse_url($url, PHP_URL_QUERY), $queryParams); + + //The real URL is stored in the 'event_link' query parameter + if (isset($queryParams['event_link']) && trim($queryParams['event_link']) !== '') { + $url = $queryParams['event_link']; + + //Replace any spaces in the URL by %20 to avoid invalid URLs + return str_replace(' ', '%20', $url); + } + } + + //Otherwise return the URL as it is + return $url; + } + +} diff --git a/src/Services/InfoProviderSystem/Providers/OctopartProvider.php b/src/Services/InfoProviderSystem/Providers/OctopartProvider.php index 721830c3..1142f4ef 100644 --- a/src/Services/InfoProviderSystem/Providers/OctopartProvider.php +++ b/src/Services/InfoProviderSystem/Providers/OctopartProvider.php @@ -30,8 +30,8 @@ use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; 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\Cache\Adapter\AdapterInterface; use Symfony\Component\HttpClient\HttpOptions; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -115,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, + ) { } @@ -171,7 +170,8 @@ class OctopartProvider implements InfoProviderInterface '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 ]; } @@ -184,7 +184,8 @@ class OctopartProvider implements InfoProviderInterface { //The client ID has to be set and a token has to be available (user clicked connect) //return /*!empty($this->clientId) && */ $this->authTokenManager->hasToken(self::OAUTH_APP_NAME); - return !empty($this->clientId) && !empty($this->secret); + return $this->settings->clientId !== null && $this->settings->clientId !== '' + && $this->settings->secret !== null && $this->settings->secret !== ''; } private function mapLifeCycleStatus(?string $value): ?ManufacturingStatus @@ -210,7 +211,7 @@ class OctopartProvider implements InfoProviderInterface $item = $this->partInfoCache->getItem($key); $item->set($part); - $item->expiresAfter(3600 * 24 * 1); //Cache for 1 day + $item->expiresAfter(3600 * 24); //Cache for 1 day $this->partInfoCache->save($item); } @@ -244,11 +245,14 @@ class OctopartProvider implements InfoProviderInterface //If we encounter the mass spec, we save it for later if ($spec['attribute']['shortname'] === "weight") { $mass = (float) $spec['siValue']; - } else if ($spec['attribute']['shortname'] === "case_package") { //Package + } elseif ($spec['attribute']['shortname'] === "case_package") { + //Package $package = $spec['value']; - } else if ($spec['attribute']['shortname'] === "numberofpins") { //Pin Count + } elseif ($spec['attribute']['shortname'] === "numberofpins") { + //Pin Count $pinCount = $spec['value']; - } else if ($spec['attribute']['shortname'] === "lifecyclestatus") { //LifeCycleStatus + } elseif ($spec['attribute']['shortname'] === "lifecyclestatus") { + //LifeCycleStatus $mStatus = $this->mapLifeCycleStatus($spec['value']); } @@ -295,8 +299,8 @@ class OctopartProvider implements InfoProviderInterface //Built the category full path $category = null; if (!empty($part['category']['name'])) { - $category = implode(' -> ', array_map(fn($c) => $c['name'], $part['category']['ancestors'] ?? [])); - if (!empty($category)) { + $category = implode(' -> ', array_map(static fn($c) => $c['name'], $part['category']['ancestors'] ?? [])); + if ($category !== '' && $category !== '0') { $category .= ' -> '; } $category .= $part['category']['name']; @@ -335,7 +339,7 @@ class OctopartProvider implements InfoProviderInterface ) { hits results { - part + part %s } } @@ -345,10 +349,10 @@ class OctopartProvider implements InfoProviderInterface $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 = []; @@ -381,9 +385,9 @@ class OctopartProvider implements InfoProviderInterface $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]); @@ -401,4 +405,4 @@ class OctopartProvider implements InfoProviderInterface 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 00000000..b74e0365 --- /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 00000000..5c8efbf1 --- /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 8c2a4430..ae2ab0d1 100644 --- a/src/Services/InfoProviderSystem/Providers/TMEClient.php +++ b/src/Services/InfoProviderSystem/Providers/TMEClient.php @@ -23,24 +23,23 @@ declare(strict_types=1); namespace App\Services\InfoProviderSystem\Providers; -use Symfony\Component\HttpClient\DecoratorTrait; +use App\Settings\InfoProviderSystem\TMESettings; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; -use Symfony\Contracts\HttpClient\ResponseStreamInterface; 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, @@ -49,13 +48,19 @@ class TMEClient public function isUsable(): bool { - if ($this->token === '' || $this->secret === '') { - return false; - } - - return true; + 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. @@ -89,4 +94,4 @@ class TMEClient 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 2d12b222..9bc73f09 100644 --- a/src/Services/InfoProviderSystem/Providers/TMEProvider.php +++ b/src/Services/InfoProviderSystem/Providers/TMEProvider.php @@ -24,26 +24,28 @@ 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 Symfony\Contracts\HttpClient\HttpClientInterface; +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 @@ -52,7 +54,8 @@ class TMEProvider implements InfoProviderInterface '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 ]; } @@ -69,8 +72,8 @@ class TMEProvider implements InfoProviderInterface 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, ]); @@ -82,7 +85,7 @@ class TMEProvider implements InfoProviderInterface $result[] = new SearchResultDTO( provider_key: $this->getProviderKey(), provider_id: $product['Symbol'], - name: !empty($product['OriginalSymbol']) ? $product['OriginalSymbol'] : $product['Symbol'], + name: empty($product['OriginalSymbol']) ? $product['Symbol'] : $product['OriginalSymbol'], description: $product['Description'], category: $product['Category'], manufacturer: $product['Producer'], @@ -99,8 +102,8 @@ class TMEProvider implements InfoProviderInterface 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], ]); @@ -118,7 +121,7 @@ class TMEProvider implements InfoProviderInterface return new PartDetailDTO( provider_key: $this->getProviderKey(), provider_id: $product['Symbol'], - name: !empty($product['OriginalSymbol']) ? $product['OriginalSymbol'] : $product['Symbol'], + name: empty($product['OriginalSymbol']) ? $product['Symbol'] : $product['OriginalSymbol'], description: $product['Description'], category: $product['Category'], manufacturer: $product['Producer'], @@ -144,8 +147,8 @@ class TMEProvider implements InfoProviderInterface 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], ]); @@ -186,9 +189,9 @@ class TMEProvider implements InfoProviderInterface 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], ]); @@ -229,8 +232,8 @@ class TMEProvider implements InfoProviderInterface 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], ]); @@ -293,4 +296,4 @@ class TMEProvider implements InfoProviderInterface ProviderCapabilities::PRICE, ]; } -} \ No newline at end of file +} diff --git a/src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php new file mode 100644 index 00000000..2de7c035 --- /dev/null +++ b/src/Services/LabelSystem/BarcodeScanner/BarcodeRedirector.php @@ -0,0 +1,166 @@ +. + */ + +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\Services\LabelSystem\BarcodeScanner; + +use App\Entity\LabelSystem\LabelSupportedElement; +use App\Entity\Parts\Manufacturer; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityNotFoundException; +use InvalidArgumentException; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeRedirectorTest + */ +final class BarcodeRedirector +{ + public function __construct(private readonly UrlGeneratorInterface $urlGenerator, private readonly EntityManagerInterface $em) + { + } + + /** + * Determines the URL to which the user should be redirected, when scanning a QR code. + * + * @param BarcodeScanResultInterface $barcodeScan The result of the barcode scan + * @return string the URL to which should be redirected + * + * @throws EntityNotFoundException + */ + public function getRedirectURL(BarcodeScanResultInterface $barcodeScan): string + { + if($barcodeScan instanceof LocalBarcodeScanResult) { + return $this->getURLLocalBarcode($barcodeScan); + } + + if ($barcodeScan instanceof EIGP114BarcodeScanResult) { + return $this->getURLVendorBarcode($barcodeScan); + } + + throw new InvalidArgumentException('Unknown $barcodeScan type: '.get_class($barcodeScan)); + } + + private function getURLLocalBarcode(LocalBarcodeScanResult $barcodeScan): string + { + switch ($barcodeScan->target_type) { + case LabelSupportedElement::PART: + return $this->urlGenerator->generate('app_part_show', ['id' => $barcodeScan->target_id]); + case LabelSupportedElement::PART_LOT: + //Try to determine the part to the given lot + $lot = $this->em->find(PartLot::class, $barcodeScan->target_id); + if (!$lot instanceof PartLot) { + throw new EntityNotFoundException(); + } + + return $this->urlGenerator->generate('app_part_show', ['id' => $lot->getPart()->getID()]); + + case LabelSupportedElement::STORELOCATION: + return $this->urlGenerator->generate('part_list_store_location', ['id' => $barcodeScan->target_id]); + + default: + throw new InvalidArgumentException('Unknown $type: '.$barcodeScan->target_type->name); + } + } + + /** + * Gets the URL to a part from a scan of a Vendor Barcode + */ + private function getURLVendorBarcode(EIGP114BarcodeScanResult $barcodeScan): string + { + $part = $this->getPartFromVendor($barcodeScan); + return $this->urlGenerator->generate('app_part_show', ['id' => $part->getID()]); + } + + /** + * Gets a part from a scan of a Vendor Barcode by filtering for parts + * with the same Info Provider Id or, if that fails, by looking for parts with a + * matching manufacturer product number. Only returns the first matching part. + */ + private function getPartFromVendor(EIGP114BarcodeScanResult $barcodeScan) : Part + { + // first check via the info provider ID (e.g. Vendor ID). This might fail if the part was not added via + // the info provider system or if the part was bought from a different vendor than the data was retrieved + // from. + if($barcodeScan->digikeyPartNumber) { + $qb = $this->em->getRepository(Part::class)->createQueryBuilder('part'); + //Lower() to be case insensitive + $qb->where($qb->expr()->like('LOWER(part.providerReference.provider_id)', 'LOWER(:vendor_id)')); + $qb->setParameter('vendor_id', $barcodeScan->digikeyPartNumber); + $results = $qb->getQuery()->getResult(); + if ($results) { + return $results[0]; + } + } + + if(!$barcodeScan->supplierPartNumber){ + throw new EntityNotFoundException(); + } + + //Fallback to the manufacturer part number. This may return false positives, since it is common for + //multiple manufacturers to use the same part number for their version of a common product + //We assume the user is able to realize when this returns the wrong part + //If the barcode specifies the manufacturer we try to use that as well + $mpnQb = $this->em->getRepository(Part::class)->createQueryBuilder('part'); + $mpnQb->where($mpnQb->expr()->like('LOWER(part.manufacturer_product_number)', 'LOWER(:mpn)')); + $mpnQb->setParameter('mpn', $barcodeScan->supplierPartNumber); + + if($barcodeScan->mouserManufacturer){ + $manufacturerQb = $this->em->getRepository(Manufacturer::class)->createQueryBuilder("manufacturer"); + $manufacturerQb->where($manufacturerQb->expr()->like("LOWER(manufacturer.name)", "LOWER(:manufacturer_name)")); + $manufacturerQb->setParameter("manufacturer_name", $barcodeScan->mouserManufacturer); + $manufacturers = $manufacturerQb->getQuery()->getResult(); + + if($manufacturers) { + $mpnQb->andWhere($mpnQb->expr()->eq("part.manufacturer", ":manufacturer")); + $mpnQb->setParameter("manufacturer", $manufacturers); + } + + } + + $results = $mpnQb->getQuery()->getResult(); + if($results){ + return $results[0]; + } + throw new EntityNotFoundException(); + } +} diff --git a/src/Services/LabelSystem/BarcodeScanner/BarcodeScanHelper.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanHelper.php new file mode 100644 index 00000000..e5930b36 --- /dev/null +++ b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanHelper.php @@ -0,0 +1,243 @@ +. + */ + +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\Services\LabelSystem\BarcodeScanner; + +use App\Entity\LabelSystem\LabelSupportedElement; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use Doctrine\ORM\EntityManagerInterface; +use InvalidArgumentException; + +/** + * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeScanHelperTest + */ +final class BarcodeScanHelper +{ + private const PREFIX_TYPE_MAP = [ + 'L' => LabelSupportedElement::PART_LOT, + 'P' => LabelSupportedElement::PART, + 'S' => LabelSupportedElement::STORELOCATION, + ]; + + public const QR_TYPE_MAP = [ + 'lot' => LabelSupportedElement::PART_LOT, + 'part' => LabelSupportedElement::PART, + 'location' => LabelSupportedElement::STORELOCATION, + ]; + + public function __construct(private readonly EntityManagerInterface $entityManager) + { + } + + /** + * Parse the given barcode content and return the target type and ID. + * If the barcode could not be parsed, an exception is thrown. + * Using the $type parameter, you can specify how the barcode should be parsed. If set to null, the function + * will try to guess the type. + * @param string $input + * @param BarcodeSourceType|null $type + * @return BarcodeScanResultInterface + */ + public function scanBarcodeContent(string $input, ?BarcodeSourceType $type = null): BarcodeScanResultInterface + { + //Do specific parsing + if ($type === BarcodeSourceType::INTERNAL) { + return $this->parseInternalBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode'); + } + if ($type === BarcodeSourceType::USER_DEFINED) { + return $this->parseUserDefinedBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode'); + } + if ($type === BarcodeSourceType::IPN) { + return $this->parseIPNBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode'); + } + if ($type === BarcodeSourceType::EIGP114) { + return $this->parseEIGP114Barcode($input); + } + + //Null means auto and we try the different formats + $result = $this->parseInternalBarcode($input); + + if ($result !== null) { + return $result; + } + + //Try to parse as User defined barcode + $result = $this->parseUserDefinedBarcode($input); + if ($result !== null) { + return $result; + } + + //If the barcode is formatted as EIGP114, we can parse it directly + if (EIGP114BarcodeScanResult::isFormat06Code($input)) { + return $this->parseEIGP114Barcode($input); + } + + //Try to parse as IPN barcode + $result = $this->parseIPNBarcode($input); + if ($result !== null) { + return $result; + } + + throw new InvalidArgumentException('Unknown barcode'); + } + + private function parseEIGP114Barcode(string $input): EIGP114BarcodeScanResult + { + return EIGP114BarcodeScanResult::parseFormat06Code($input); + } + + private function parseUserDefinedBarcode(string $input): ?LocalBarcodeScanResult + { + $lot_repo = $this->entityManager->getRepository(PartLot::class); + //Find only the first result + $results = $lot_repo->findBy(['user_barcode' => $input], limit: 1); + + if (count($results) === 0) { + return null; + } + //We found a part, so use it to create the result + $lot = $results[0]; + + return new LocalBarcodeScanResult( + target_type: LabelSupportedElement::PART_LOT, + target_id: $lot->getID(), + source_type: BarcodeSourceType::USER_DEFINED + ); + } + + private function parseIPNBarcode(string $input): ?LocalBarcodeScanResult + { + $part_repo = $this->entityManager->getRepository(Part::class); + //Find only the first result + $results = $part_repo->findBy(['ipn' => $input], limit: 1); + + if (count($results) === 0) { + return null; + } + //We found a part, so use it to create the result + $part = $results[0]; + + return new LocalBarcodeScanResult( + target_type: LabelSupportedElement::PART, + target_id: $part->getID(), + source_type: BarcodeSourceType::IPN + ); + } + + /** + * This function tries to interpret the given barcode content as an internal barcode. + * If the barcode could not be parsed at all, null is returned. If the barcode is a valid format, but could + * not be found in the database, an exception is thrown. + * @param string $input + * @return LocalBarcodeScanResult|null + */ + private function parseInternalBarcode(string $input): ?LocalBarcodeScanResult + { + $input = trim($input); + $matches = []; + + //Some scanner output '-' as ß, so replace it (ß is never used, so we can replace it safely) + $input = str_replace('ß', '-', $input); + + //Extract parts from QR code's URL + if (preg_match('#^https?://.*/scan/(\w+)/(\d+)/?$#', $input, $matches)) { + return new LocalBarcodeScanResult( + target_type: self::QR_TYPE_MAP[strtolower($matches[1])], + target_id: (int) $matches[2], + source_type: BarcodeSourceType::INTERNAL + ); + } + + //New Code39 barcode use L0001 format + if (preg_match('#^([A-Z])(\d{4,})$#', $input, $matches)) { + $prefix = $matches[1]; + $id = (int) $matches[2]; + + if (!isset(self::PREFIX_TYPE_MAP[$prefix])) { + throw new InvalidArgumentException('Unknown prefix '.$prefix); + } + + return new LocalBarcodeScanResult( + target_type: self::PREFIX_TYPE_MAP[$prefix], + target_id: $id, + source_type: BarcodeSourceType::INTERNAL + ); + } + + //During development the L-000001 format was used + if (preg_match('#^(\w)-(\d{6,})$#', $input, $matches)) { + $prefix = $matches[1]; + $id = (int) $matches[2]; + + if (!isset(self::PREFIX_TYPE_MAP[$prefix])) { + throw new InvalidArgumentException('Unknown prefix '.$prefix); + } + + return new LocalBarcodeScanResult( + target_type: self::PREFIX_TYPE_MAP[$prefix], + target_id: $id, + source_type: BarcodeSourceType::INTERNAL + ); + } + + //Legacy Part-DB location labels used $L00336 format + if (preg_match('#^\$L(\d{5,})$#', $input, $matches)) { + return new LocalBarcodeScanResult( + target_type: LabelSupportedElement::STORELOCATION, + target_id: (int) $matches[1], + source_type: BarcodeSourceType::INTERNAL + ); + } + + //Legacy Part-DB used EAN8 barcodes for part labels. Format 0000001(2) (note the optional 8th digit => checksum) + if (preg_match('#^(\d{7})\d?$#', $input, $matches)) { + return new LocalBarcodeScanResult( + target_type: LabelSupportedElement::PART, + target_id: (int) $matches[1], + source_type: BarcodeSourceType::INTERNAL + ); + } + + //This function abstain from further parsing + return null; + } +} diff --git a/src/Services/LabelSystem/BarcodeScanner/BarcodeScanResultInterface.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanResultInterface.php new file mode 100644 index 00000000..88130351 --- /dev/null +++ b/src/Services/LabelSystem/BarcodeScanner/BarcodeScanResultInterface.php @@ -0,0 +1,36 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\LabelSystem\BarcodeScanner; + +interface BarcodeScanResultInterface +{ + /** + * Returns all data that was decoded from the barcode in a format, that can be shown in a table to the user. + * The return values of this function are not meant to be parsed by code again, but should just give a information + * to the user. + * The keys of the returned array are the first column of the table and the values are the second column. + * @return array + */ + public function getDecodedForInfoMode(): array; +} \ No newline at end of file diff --git a/src/Services/LabelSystem/BarcodeScanner/BarcodeSourceType.php b/src/Services/LabelSystem/BarcodeScanner/BarcodeSourceType.php new file mode 100644 index 00000000..40f707de --- /dev/null +++ b/src/Services/LabelSystem/BarcodeScanner/BarcodeSourceType.php @@ -0,0 +1,45 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\LabelSystem\BarcodeScanner; + +/** + * This enum represents the different types, where a barcode/QR-code can be generated from + */ +enum BarcodeSourceType +{ + /** This Barcode was generated using Part-DB internal recommended barcode generator */ + case INTERNAL; + /** This barcode is containing an internal part number (IPN) */ + case IPN; + + /** + * This barcode is a user defined barcode defined on a part lot + */ + case USER_DEFINED; + + /** + * EIGP114 formatted barcodes like used by digikey, mouser, etc. + */ + case EIGP114; +} \ No newline at end of file diff --git a/src/Services/LabelSystem/BarcodeScanner/EIGP114BarcodeScanResult.php b/src/Services/LabelSystem/BarcodeScanner/EIGP114BarcodeScanResult.php new file mode 100644 index 00000000..0b4f4b56 --- /dev/null +++ b/src/Services/LabelSystem/BarcodeScanner/EIGP114BarcodeScanResult.php @@ -0,0 +1,332 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\LabelSystem\BarcodeScanner; + +/** + * This class represents the content of a EIGP114 barcode. + * Based on PR 811, EIGP 114.2018 (https://www.ecianow.org/assets/docs/GIPC/EIGP-114.2018%20ECIA%20Labeling%20Specification%20for%20Product%20and%20Shipment%20Identification%20in%20the%20Electronics%20Industry%20-%202D%20Barcode.pdf), + * , https://forum.digikey.com/t/digikey-product-labels-decoding-digikey-barcodes/41097 + */ +class EIGP114BarcodeScanResult implements BarcodeScanResultInterface +{ + + /** + * @var string|null Ship date in format YYYYMMDD + */ + public readonly ?string $shipDate; + + /** + * @var string|null Customer assigned part number – Optional based on + * agreements between Distributor and Supplier + */ + public readonly ?string $customerPartNumber; + + /** + * @var string|null Supplier assigned part number + */ + public readonly ?string $supplierPartNumber; + + /** + * @var int|null Quantity of product + */ + public readonly ?int $quantity; + + /** + * @var string|null Customer assigned purchase order number + */ + public readonly ?string $customerPO; + + /** + * @var string|null Line item number from PO. Required on Logistic Label when + * used on back of Packing Slip. See Section 4.9 + */ + public readonly ?string $customerPOLine; + + /** + * 9D - YYWW (Year and Week of Manufacture). ) If no date code is used + * for a particular part, this field should be populated with N/T + * to indicate the product is Not Traceable by this data field. + * @var string|null + */ + public readonly ?string $dateCode; + + /** + * 10D - YYWW (Year and Week of Manufacture). ) If no date code is used + * for a particular part, this field should be populated with N/T + * to indicate the product is Not Traceable by this data field. + * @var string|null + */ + public readonly ?string $alternativeDateCode; + + /** + * Traceability number assigned to a batch or group of items. If + * no lot code is used for a particular part, this field should be + * populated with N/T to indicate the product is Not Traceable + * by this data field. + * @var string|null + */ + public readonly ?string $lotCode; + + /** + * Country where part was manufactured. Two-letter code from + * ISO 3166 country code list + * @var string|null + */ + public readonly ?string $countryOfOrigin; + + /** + * @var string|null Unique alphanumeric number assigned by supplier + * 3S - Package ID for Inner Pack when part of a mixed Logistic + * Carton. Always used in conjunction with a mixed logistic label + * with a 5S data identifier for Package ID. + */ + public readonly ?string $packageId1; + + /** + * @var string|null + * 4S - Package ID for Logistic Carton with like items + */ + public readonly ?string $packageId2; + + /** + * @var string|null + * 5S - Package ID for Logistic Carton with mixed items + */ + public readonly ?string $packageId3; + + /** + * @var string|null Unique alphanumeric number assigned by supplier. + */ + public readonly ?string $packingListNumber; + + /** + * @var string|null Ship date in format YYYYMMDD + */ + public readonly ?string $serialNumber; + + /** + * @var string|null Code for sorting and classifying LEDs. Use when applicable + */ + public readonly ?string $binCode; + + /** + * @var int|null Sequential carton count in format “#/#” or “# of #” + */ + public readonly ?int $packageCount; + + /** + * @var string|null Alphanumeric string assigned by the supplier to distinguish + * from one closely-related design variation to another. Use as + * required or when applicable + */ + public readonly ?string $revisionNumber; + + /** + * @var string|null Digikey Extension: This is not represented in the ECIA spec, but the field being used is found in the ANSI MH10.8.2-2016 spec on which the ECIA spec is based. In the ANSI spec it is called First Level (Supplier Assigned) Part Number. + */ + public readonly ?string $digikeyPartNumber; + + /** + * @var string|null Digikey Extension: This can be shared across multiple invoices and time periods and is generated as an order enters our system from any vector (web, API, phone order, etc.) + */ + public readonly ?string $digikeySalesOrderNumber; + + /** + * @var string|null Digikey extension: This is typically assigned per shipment as items are being released to be picked in the warehouse. A SO can have many Invoice numbers + */ + public readonly ?string $digikeyInvoiceNumber; + + /** + * @var string|null Digikey extension: This is for internal DigiKey purposes and defines the label type. + */ + public readonly ?string $digikeyLabelType; + + /** + * @var string|null You will also see this as the last part of a URL for a product detail page. Ex https://www.digikey.com/en/products/detail/w%C3%BCrth-elektronik/860010672008/5726907 + */ + public readonly ?string $digikeyPartID; + + /** + * @var string|null Digikey Extension: For internal use of Digikey. Probably not needed + */ + public readonly ?string $digikeyNA; + + /** + * @var string|null Digikey Extension: This is a field of varying length used to keep the barcode approximately the same size between labels. It is safe to ignore. + */ + public readonly ?string $digikeyPadding; + + public readonly ?string $mouserPositionInOrder; + + public readonly ?string $mouserManufacturer; + + + + /** + * + * @param array $data The fields of the EIGP114 barcode, where the key is the field name and the value is the field content + */ + public function __construct(public readonly array $data) + { + //IDs per EIGP 114.2018 + $this->shipDate = $data['6D'] ?? null; + $this->customerPartNumber = $data['P'] ?? null; + $this->supplierPartNumber = $data['1P'] ?? null; + $this->quantity = isset($data['Q']) ? (int)$data['Q'] : null; + $this->customerPO = $data['K'] ?? null; + $this->customerPOLine = $data['4K'] ?? null; + $this->dateCode = $data['9D'] ?? null; + $this->alternativeDateCode = $data['10D'] ?? null; + $this->lotCode = $data['1T'] ?? null; + $this->countryOfOrigin = $data['4L'] ?? null; + $this->packageId1 = $data['3S'] ?? null; + $this->packageId2 = $data['4S'] ?? null; + $this->packageId3 = $data['5S'] ?? null; + $this->packingListNumber = $data['11K'] ?? null; + $this->serialNumber = $data['S'] ?? null; + $this->binCode = $data['33P'] ?? null; + $this->packageCount = isset($data['13Q']) ? (int)$data['13Q'] : null; + $this->revisionNumber = $data['2P'] ?? null; + //IDs used by Digikey + $this->digikeyPartNumber = $data['30P'] ?? null; + $this->digikeySalesOrderNumber = $data['1K'] ?? null; + $this->digikeyInvoiceNumber = $data['10K'] ?? null; + $this->digikeyLabelType = $data['11Z'] ?? null; + $this->digikeyPartID = $data['12Z'] ?? null; + $this->digikeyNA = $data['13Z'] ?? null; + $this->digikeyPadding = $data['20Z'] ?? null; + //IDs used by Mouser + $this->mouserPositionInOrder = $data['14K'] ?? null; + $this->mouserManufacturer = $data['1V'] ?? null; + } + + /** + * Tries to guess the vendor of the barcode based on the supplied data field. + * This is experimental and should not be relied upon. + * @return string|null The guessed vendor as smallcase string (e.g. "digikey", "mouser", etc.), or null if the vendor could not be guessed + */ + public function guessBarcodeVendor(): ?string + { + //If the barcode data contains the digikey extensions, we assume it is a digikey barcode + if (isset($this->data['13Z']) || isset($this->data['20Z']) || isset($this->data['12Z']) || isset($this->data['11Z'])) { + return 'digikey'; + } + + //If the barcode data contains the mouser extensions, we assume it is a mouser barcode + if (isset($this->data['14K']) || isset($this->data['1V'])) { + return 'mouser'; + } + + //According to this thread (https://github.com/inventree/InvenTree/issues/853), Newark/element14 codes contains a "3P" field + if (isset($this->data['3P'])) { + return 'element14'; + } + + return null; + } + + /** + * Checks if the given input is a valid format06 formatted data. + * This just perform a simple check for the header, the content might be malformed still. + * @param string $input + * @return bool + */ + public static function isFormat06Code(string $input): bool + { + //Code must begin with [)>06 + if(!str_starts_with($input, "[)>\u{1E}06\u{1D}")){ + return false; + } + + //Digikey does not put a trailer onto the barcode, so we just check for the header + + return true; + } + + /** + * Parses a format06 code a returns a new instance of this class + * @param string $input + * @return self + */ + public static function parseFormat06Code(string $input): self + { + //Ensure that the input is a valid format06 code + if (!self::isFormat06Code($input)) { + throw new \InvalidArgumentException("The given input is not a valid format06 code"); + } + + //Remove the trailer, if present + if (str_ends_with($input, "\u{1E}\u{04}")){ + $input = substr($input, 5, -2); + } + + //Split the input into the different fields (using the separator) + $parts = explode("\u{1D}", $input); + + //The first field is the format identifier, which we do not need + array_shift($parts); + + //Split the fields into key-value pairs + $results = []; + + foreach($parts as $part) { + //^ 0* ([1-9]? \d* [A-Z]) + //Start of the string Leading zeros are discarded Not a zero Any number of digits single uppercase Letter + // 00 1 4 K + + if(!preg_match('/^0*([1-9]?\d*[A-Z])/', $part, $matches)) { + throw new \LogicException("Could not parse field: $part"); + } + //Extract the key + $key = $matches[0]; + //Extract the field value + $fieldValue = substr($part, strlen($matches[0])); + + $results[$key] = $fieldValue; + } + + return new self($results); + } + + public function getDecodedForInfoMode(): array + { + $tmp = [ + 'Barcode type' => 'EIGP114', + 'Guessed vendor from barcode' => $this->guessBarcodeVendor() ?? 'Unknown', + ]; + + //Iterate over all fields of this object and add them to the array if they are not null + foreach((array) $this as $key => $value) { + //Skip data key + if ($key === 'data') { + continue; + } + if($value !== null) { + $tmp[$key] = $value; + } + } + + return $tmp; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/BarcodeScanner/LocalBarcodeScanResult.php b/src/Services/LabelSystem/BarcodeScanner/LocalBarcodeScanResult.php new file mode 100644 index 00000000..050aff6f --- /dev/null +++ b/src/Services/LabelSystem/BarcodeScanner/LocalBarcodeScanResult.php @@ -0,0 +1,49 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\LabelSystem\BarcodeScanner; + +use App\Entity\LabelSystem\LabelSupportedElement; + +/** + * This class represents the result of a barcode scan of a barcode that uniquely identifies a local entity, + * like an internally generated barcode or a barcode that was added manually to the system by a user + */ +class LocalBarcodeScanResult implements BarcodeScanResultInterface +{ + public function __construct( + public readonly LabelSupportedElement $target_type, + public readonly int $target_id, + public readonly BarcodeSourceType $source_type, + ) { + } + + public function getDecodedForInfoMode(): array + { + return [ + 'Barcode type' => $this->source_type->name, + 'Target type' => $this->target_type->name, + 'Target ID' => $this->target_id, + ]; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php index 5acf98f1..3df7d227 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php @@ -44,7 +44,7 @@ namespace App\Services\LabelSystem\Barcodes; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use InvalidArgumentException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -56,13 +56,13 @@ final class BarcodeContentGenerator public const PREFIX_MAP = [ Part::class => 'P', PartLot::class => 'L', - Storelocation::class => 'S', + StorageLocation::class => 'S', ]; private const URL_MAP = [ Part::class => 'part', PartLot::class => 'lot', - Storelocation::class => 'location', + StorageLocation::class => 'location', ]; public function __construct(private readonly UrlGeneratorInterface $urlGenerator) @@ -76,11 +76,11 @@ final class BarcodeContentGenerator { $type = $this->classToString(self::URL_MAP, $target); - return $this->urlGenerator->generate('scan_qr', [ - 'type' => $type, + return $this->urlGenerator->generate('scan_qr', [ + 'type' => $type, 'id' => $target->getID() ?? 0, '_locale' => null, -], UrlGeneratorInterface::ABSOLUTE_URL); + ], UrlGeneratorInterface::ABSOLUTE_URL); } /** @@ -95,6 +95,11 @@ final class BarcodeContentGenerator return $prefix.$id; } + /** + * @param array $map + * @param object $target + * @return string + */ private function classToString(array $map, object $target): string { $class = $target::class; diff --git a/src/Services/LabelSystem/Barcodes/BarcodeHelper.php b/src/Services/LabelSystem/Barcodes/BarcodeHelper.php new file mode 100644 index 00000000..c9fe64f3 --- /dev/null +++ b/src/Services/LabelSystem/Barcodes/BarcodeHelper.php @@ -0,0 +1,97 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\LabelSystem\Barcodes; + +use App\Entity\LabelSystem\BarcodeType; +use Com\Tecnick\Barcode\Barcode; + +/** + * This function is used to generate barcodes of various types using arbitrary (text) content. + * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeHelperTest + */ +class BarcodeHelper +{ + + /** + * Generates a barcode with the given content and type and returns it as SVG string. + * @param string $content + * @param BarcodeType $type + * @return string + */ + public function barcodeAsSVG(string $content, BarcodeType $type): string + { + $barcode = new Barcode(); + + $type_str = match ($type) { + BarcodeType::NONE => throw new \InvalidArgumentException('Barcode type must not be NONE! This would make no sense...'), + BarcodeType::QR => 'QRCODE', + BarcodeType::DATAMATRIX => 'DATAMATRIX', + BarcodeType::CODE39 => 'C39', + BarcodeType::CODE93 => 'C93', + BarcodeType::CODE128 => 'C128A', + }; + + return $barcode->getBarcodeObj($type_str, $content)->getSvgCode(); + } + + /** + * Generates a barcode with the given content and type and returns it as HTML image tag. + * @param string $content + * @param BarcodeType $type + * @param string $width Width of the image tag + * @param string|null $alt_text The alt text of the image tag. If null, the content is used. + * @return string + */ + public function barcodeAsHTML(string $content, BarcodeType $type, string $width = '100%', ?string $alt_text = null): string + { + $svg = $this->barcodeAsSVG($content, $type); + $base64 = $this->dataUri($svg, 'image/svg+xml'); + $alt_text ??= $content; + + return ''.$alt_text.''; + } + + /** + * Creates a data URI (RFC 2397). + * Based on the Twig implementation from HTMLExtension + * + * Length validation is not performed on purpose, validation should + * be done before calling this filter. + * + * @return string The generated data URI + */ + private function dataUri(string $data, string $mime): string + { + $repr = 'data:'; + + $repr .= $mime; + if (str_starts_with($mime, 'text/')) { + $repr .= ','.rawurlencode($data); + } else { + $repr .= ';base64,'.base64_encode($data); + } + + return $repr; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/Barcodes/BarcodeNormalizer.php b/src/Services/LabelSystem/Barcodes/BarcodeNormalizer.php deleted file mode 100644 index a5b6cb5e..00000000 --- a/src/Services/LabelSystem/Barcodes/BarcodeNormalizer.php +++ /dev/null @@ -1,110 +0,0 @@ -. - */ - -declare(strict_types=1); - -/** - * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). - * - * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -namespace App\Services\LabelSystem\Barcodes; - -use InvalidArgumentException; - -/** - * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeNormalizerTest - */ -final class BarcodeNormalizer -{ - private const PREFIX_TYPE_MAP = [ - 'L' => 'lot', - 'P' => 'part', - 'S' => 'location', - ]; - - /** - * Parses barcode content and normalizes it. - * Returns an array in the format ['part', 1]: First entry contains element type, second the ID of the element. - */ - public function normalizeBarcodeContent(string $input): array - { - $input = trim($input); - $matches = []; - - //Some scanner output '-' as ß, so replace it (ß is never used, so we can replace it safely) - $input = str_replace('ß', '-', $input); - - //Extract parts from QR code's URL - if (preg_match('#^https?://.*/scan/(\w+)/(\d+)/?$#', $input, $matches)) { - return [$matches[1], (int) $matches[2]]; - } - - //New Code39 barcode use L0001 format - if (preg_match('#^([A-Z])(\d{4,})$#', $input, $matches)) { - $prefix = $matches[1]; - $id = (int) $matches[2]; - - if (!isset(self::PREFIX_TYPE_MAP[$prefix])) { - throw new InvalidArgumentException('Unknown prefix '.$prefix); - } - - return [self::PREFIX_TYPE_MAP[$prefix], $id]; - } - - //During development the L-000001 format was used - if (preg_match('#^(\w)-(\d{6,})$#', $input, $matches)) { - $prefix = $matches[1]; - $id = (int) $matches[2]; - - if (!isset(self::PREFIX_TYPE_MAP[$prefix])) { - throw new InvalidArgumentException('Unknown prefix '.$prefix); - } - - return [self::PREFIX_TYPE_MAP[$prefix], $id]; - } - - //Legacy Part-DB location labels used $L00336 format - if (preg_match('#^\$L(\d{5,})$#', $input, $matches)) { - return ['location', (int) $matches[1]]; - } - - //Legacy Part-DB used EAN8 barcodes for part labels. Format 0000001(2) (note the optional 8th digit => checksum) - if (preg_match('#^(\d{7})\d?$#', $input, $matches)) { - return ['part', (int) $matches[1]]; - } - - throw new InvalidArgumentException('Unknown barcode format!'); - } -} diff --git a/src/Services/LabelSystem/DompdfFactory.php b/src/Services/LabelSystem/DompdfFactory.php index 4017a393..a2c8c3cd 100644 --- a/src/Services/LabelSystem/DompdfFactory.php +++ b/src/Services/LabelSystem/DompdfFactory.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\LabelSystem; use Dompdf\Dompdf; @@ -27,7 +29,7 @@ use Symfony\Component\DependencyInjection\Attribute\AsDecorator; #[AsDecorator(decorates: DompdfFactoryInterface::class)] class DompdfFactory implements DompdfFactoryInterface { - public function __construct(private string $fontDirectory, private string $tmpDirectory) + public function __construct(private readonly string $fontDirectory, private readonly string $tmpDirectory) { //Create folder if it does not exist $this->createDirectoryIfNotExisting($this->fontDirectory); @@ -36,10 +38,8 @@ class DompdfFactory implements DompdfFactoryInterface private function createDirectoryIfNotExisting(string $path): void { - if (!is_dir($path)) { - if (!mkdir($concurrentDirectory = $path, 0777, true) && !is_dir($concurrentDirectory)) { - throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory)); - } + if (!is_dir($path) && (!mkdir($concurrentDirectory = $path, 0777, true) && !is_dir($concurrentDirectory))) { + throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory)); } } @@ -51,4 +51,4 @@ class DompdfFactory implements DompdfFactoryInterface 'tempDir' => $this->tmpDirectory, ]); } -} \ No newline at end of file +} diff --git a/src/Services/LabelSystem/BarcodeGenerator.php b/src/Services/LabelSystem/LabelBarcodeGenerator.php similarity index 60% rename from src/Services/LabelSystem/BarcodeGenerator.php rename to src/Services/LabelSystem/LabelBarcodeGenerator.php index f955955a..66f74e58 100644 --- a/src/Services/LabelSystem/BarcodeGenerator.php +++ b/src/Services/LabelSystem/LabelBarcodeGenerator.php @@ -42,71 +42,49 @@ declare(strict_types=1); namespace App\Services\LabelSystem; use App\Entity\Base\AbstractDBElement; -use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\LabelSystem\BarcodeType; use App\Entity\LabelSystem\LabelOptions; use App\Services\LabelSystem\Barcodes\BarcodeContentGenerator; -use Com\Tecnick\Barcode\Barcode; +use App\Services\LabelSystem\Barcodes\BarcodeHelper; use InvalidArgumentException; /** - * @see \App\Tests\Services\LabelSystem\BarcodeGeneratorTest + * @see \App\Tests\Services\LabelSystem\LabelBarcodeGeneratorTest */ -final class BarcodeGenerator +final class LabelBarcodeGenerator { - public function __construct(private readonly BarcodeContentGenerator $barcodeContentGenerator) + public function __construct(private readonly BarcodeContentGenerator $barcodeContentGenerator, private readonly BarcodeHelper $barcodeHelper) { } - public function generateHTMLBarcode(LabelOptions $options, object $target): ?string - { - $svg = $this->generateSVG($options, $target); - $base64 = $this->dataUri($svg, 'image/svg+xml'); - return ''. $this->getContent($options, $target) . ''; - } - - /** - * Creates a data URI (RFC 2397). - * Based on the Twig implementaion from HTMLExtension - * - * Length validation is not performed on purpose, validation should - * be done before calling this filter. - * - * @return string The generated data URI + /** + * Generate the barcode for the given label as HTML image tag. + * @param LabelOptions $options + * @param AbstractDBElement $target + * @return string|null */ - private function dataUri(string $data, string $mime): string + public function generateHTMLBarcode(LabelOptions $options, AbstractDBElement $target): ?string { - $repr = 'data:'; - - $repr .= $mime; - if (str_starts_with($mime, 'text/')) { - $repr .= ','.rawurlencode($data); - } else { - $repr .= ';base64,'.base64_encode($data); - } - - return $repr; - } - - public function generateSVG(LabelOptions $options, object $target): ?string - { - $barcode = new Barcode(); - - $type = match ($options->getBarcodeType()) { - BarcodeType::NONE => null, - BarcodeType::QR => 'QRCODE', - BarcodeType::DATAMATRIX => 'DATAMATRIX', - BarcodeType::CODE39 => 'C39', - BarcodeType::CODE93 => 'C93', - BarcodeType::CODE128 => 'C128A', - }; - - if ($type === null) { + if ($options->getBarcodeType() === BarcodeType::NONE) { return null; } + return $this->barcodeHelper->barcodeAsHTML($this->getContent($options, $target), $options->getBarcodeType()); + } - return $barcode->getBarcodeObj($type, $this->getContent($options, $target))->getSvgCode(); + /** + * Generate the barcode for the given label as SVG string. + * @param LabelOptions $options + * @param AbstractDBElement $target + * @return string|null + */ + public function generateSVG(LabelOptions $options, AbstractDBElement $target): ?string + { + if ($options->getBarcodeType() === BarcodeType::NONE) { + return null; + } + + return $this->barcodeHelper->barcodeAsSVG($this->getContent($options, $target), $options->getBarcodeType()); } public function getContent(LabelOptions $options, AbstractDBElement $target): ?string diff --git a/src/Services/LabelSystem/LabelExampleElementsGenerator.php b/src/Services/LabelSystem/LabelExampleElementsGenerator.php index 61cbcc4a..d344c929 100644 --- a/src/Services/LabelSystem/LabelExampleElementsGenerator.php +++ b/src/Services/LabelSystem/LabelExampleElementsGenerator.php @@ -49,7 +49,7 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\ManufacturingStatus; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\UserSystem\User; use DateTime; use InvalidArgumentException; @@ -97,24 +97,24 @@ final class LabelExampleElementsGenerator $lot->setDescription('Example Lot'); $lot->setComment('Lot comment'); - $lot->setExpirationDate(new DateTime('+1 days')); - $lot->setStorageLocation($this->getStructuralData(Storelocation::class)); + $lot->setExpirationDate(new \DateTimeImmutable('+1 day')); + $lot->setStorageLocation($this->getStructuralData(StorageLocation::class)); $lot->setAmount(123); $lot->setOwner($this->getUser()); return $lot; } - private function getStorelocation(): Storelocation + private function getStorelocation(): StorageLocation { - $storelocation = new Storelocation(); + $storelocation = new StorageLocation(); $storelocation->setName('Location 1'); $storelocation->setComment('Example comment'); $storelocation->updateTimestamps(); $storelocation->setOwner($this->getUser()); - $parent = new Storelocation(); + $parent = new StorageLocation(); $parent->setName('Parent'); $storelocation->setParent($parent); @@ -146,11 +146,11 @@ final class LabelExampleElementsGenerator throw new InvalidArgumentException('$class must be an child of AbstractStructuralDBElement'); } - /** @var AbstractStructuralDBElement $parent */ + /** @var T $parent */ $parent = new $class(); $parent->setName('Example'); - /** @var AbstractStructuralDBElement $child */ + /** @var T $child */ $child = new $class(); $child->setName((new ReflectionClass($class))->getShortName()); $child->setParent($parent); diff --git a/src/Services/LabelSystem/LabelGenerator.php b/src/Services/LabelSystem/LabelGenerator.php index f16b135a..bfb8d27b 100644 --- a/src/Services/LabelSystem/LabelGenerator.php +++ b/src/Services/LabelSystem/LabelGenerator.php @@ -42,10 +42,6 @@ declare(strict_types=1); namespace App\Services\LabelSystem; use App\Entity\LabelSystem\LabelOptions; -use App\Entity\Parts\Part; -use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; -use Dompdf\Dompdf; use InvalidArgumentException; use Jbtronics\DompdfFontLoaderBundle\Services\DompdfFactoryInterface; @@ -66,10 +62,6 @@ final class LabelGenerator */ public function generateLabel(LabelOptions $options, object|array $elements): string { - if (!is_array($elements) && !is_object($elements)) { - throw new InvalidArgumentException('$element must be an object or an array of objects!'); - } - if (!is_array($elements)) { $elements = [$elements]; } diff --git a/src/Services/LabelSystem/LabelHTMLGenerator.php b/src/Services/LabelSystem/LabelHTMLGenerator.php index 7b6defa6..8a5201ff 100644 --- a/src/Services/LabelSystem/LabelHTMLGenerator.php +++ b/src/Services/LabelSystem/LabelHTMLGenerator.php @@ -42,6 +42,7 @@ declare(strict_types=1); 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; @@ -53,7 +54,14 @@ use Twig\Error\Error; final class LabelHTMLGenerator { - public function __construct(private readonly ElementTypeNameGenerator $elementTypeNameGenerator, private readonly LabelTextReplacer $replacer, private readonly Environment $twig, private readonly BarcodeGenerator $barcodeGenerator, private readonly SandboxedTwigProvider $sandboxedTwigProvider, private readonly Security $security, private readonly string $partdb_title) + public function __construct( + private readonly ElementTypeNameGenerator $elementTypeNameGenerator, + private readonly LabelTextReplacer $replacer, + private readonly Environment $twig, + private readonly LabelBarcodeGenerator $barcodeGenerator, + private readonly SandboxedTwigFactory $sandboxedTwigProvider, + private readonly Security $security, + private readonly CustomizationSettings $customizationSettings,) { } @@ -66,7 +74,7 @@ final class LabelHTMLGenerator $twig_elements = []; if (LabelProcessMode::TWIG === $options->getProcessMode()) { - $sandboxed_twig = $this->sandboxedTwigProvider->getTwig($options); + $sandboxed_twig = $this->sandboxedTwigProvider->createTwig($options); $current_user = $this->security->getUser(); } @@ -79,8 +87,12 @@ final class LabelHTMLGenerator [ 'element' => $element, '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(), ] ); } catch (Error $exception) { diff --git a/src/Services/LabelSystem/LabelProfileDropdownHelper.php b/src/Services/LabelSystem/LabelProfileDropdownHelper.php index d7f1120d..773923ab 100644 --- a/src/Services/LabelSystem/LabelProfileDropdownHelper.php +++ b/src/Services/LabelSystem/LabelProfileDropdownHelper.php @@ -44,15 +44,20 @@ namespace App\Services\LabelSystem; use App\Entity\LabelSystem\LabelProfile; use App\Entity\LabelSystem\LabelSupportedElement; use App\Repository\LabelProfileRepository; -use App\Services\UserSystem\UserCacheKeyGenerator; +use App\Services\Cache\ElementCacheTagGenerator; +use App\Services\Cache\UserCacheKeyGenerator; use Doctrine\ORM\EntityManagerInterface; use Symfony\Contracts\Cache\ItemInterface; use Symfony\Contracts\Cache\TagAwareCacheInterface; final class LabelProfileDropdownHelper { - public function __construct(private readonly TagAwareCacheInterface $cache, private readonly EntityManagerInterface $entityManager, private readonly UserCacheKeyGenerator $keyGenerator) - { + public function __construct( + private readonly TagAwareCacheInterface $cache, + private readonly EntityManagerInterface $entityManager, + private readonly UserCacheKeyGenerator $keyGenerator, + private readonly ElementCacheTagGenerator $tagGenerator, + ) { } /** @@ -67,10 +72,9 @@ final class LabelProfileDropdownHelper $type = LabelSupportedElement::from($type); } - $secure_class_name = str_replace('\\', '_', LabelProfile::class); + $secure_class_name = $this->tagGenerator->getElementTypeCacheTag(LabelProfile::class); $key = 'profile_dropdown_'.$this->keyGenerator->generateKey().'_'.$secure_class_name.'_'.$type->value; - - /** @var LabelProfileRepository $repo */ + $repo = $this->entityManager->getRepository(LabelProfile::class); return $this->cache->get($key, function (ItemInterface $item) use ($repo, $type, $secure_class_name) { diff --git a/src/Services/LabelSystem/LabelTextReplacer.php b/src/Services/LabelSystem/LabelTextReplacer.php index 034e243f..6f0a9ee8 100644 --- a/src/Services/LabelSystem/LabelTextReplacer.php +++ b/src/Services/LabelSystem/LabelTextReplacer.php @@ -64,6 +64,17 @@ final class LabelTextReplacer * @return string If the placeholder was valid, the replaced info. Otherwise the passed string. */ public function handlePlaceholder(string $placeholder, object $target): string + { + return $this->handlePlaceholderOrReturnNull($placeholder, $target) ?? $placeholder; + } + + /** + * Similar to handlePlaceholder, but returns null if the placeholder is not known (instead of the original string) + * @param string $placeholder + * @param object $target + * @return string|null + */ + public function handlePlaceholderOrReturnNull(string $placeholder, object $target): ?string { foreach ($this->providers as $provider) { /** @var PlaceholderProviderInterface $provider */ @@ -73,7 +84,7 @@ final class LabelTextReplacer } } - return $placeholder; + return null; } /** diff --git a/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php b/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php index 11824054..400fef35 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php @@ -24,12 +24,18 @@ namespace App\Services\LabelSystem\PlaceholderProviders; use App\Entity\LabelSystem\BarcodeType; use App\Entity\LabelSystem\LabelOptions; -use App\Services\LabelSystem\BarcodeGenerator; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use App\Services\LabelSystem\Barcodes\BarcodeHelper; +use App\Services\LabelSystem\LabelBarcodeGenerator; use App\Services\LabelSystem\Barcodes\BarcodeContentGenerator; +use Com\Tecnick\Barcode\Exception; final class BarcodeProvider implements PlaceholderProviderInterface { - public function __construct(private readonly BarcodeGenerator $barcodeGenerator, private readonly BarcodeContentGenerator $barcodeContentGenerator) + public function __construct(private readonly LabelBarcodeGenerator $barcodeGenerator, + private readonly BarcodeContentGenerator $barcodeContentGenerator, + private readonly BarcodeHelper $barcodeHelper) { } @@ -57,18 +63,61 @@ final class BarcodeProvider implements PlaceholderProviderInterface 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); return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target); } + if (($label_target instanceof Part || $label_target instanceof PartLot) + && str_starts_with($placeholder, '[[IPN_BARCODE_')) { + if ($label_target instanceof PartLot) { + $label_target = $label_target->getPart(); + } + + if ($label_target === null || $label_target->getIPN() === null || $label_target->getIPN() === '') { + //Replace with empty result, if no IPN is set + return ''; + } + + try { + //Add placeholders for the IPN barcode + if ('[[IPN_BARCODE_C39]]' === $placeholder) { + return $this->barcodeHelper->barcodeAsHTML($label_target->getIPN(), BarcodeType::CODE39); + } + if ('[[IPN_BARCODE_C128]]' === $placeholder) { + return $this->barcodeHelper->barcodeAsHTML($label_target->getIPN(), BarcodeType::CODE128); + } + if ('[[IPN_BARCODE_QR]]' === $placeholder) { + return $this->barcodeHelper->barcodeAsHTML($label_target->getIPN(), BarcodeType::QR); + } + } catch (Exception $e) { + //If an error occurs, output it + return 'IPN Barcode ERROR!: '.$e->getMessage(); + } + } + + + + return null; } } diff --git a/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php b/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php index 5a9b2294..f14a5863 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php +++ b/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php @@ -41,6 +41,7 @@ declare(strict_types=1); 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 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; */ 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(); @@ -81,7 +86,7 @@ final class GlobalProviders implements PlaceholderProviderInterface return 'anonymous'; } - $now = new DateTime(); + $now = new \DateTimeImmutable(); if ('[[DATETIME]]' === $placeholder) { $formatter = IntlDateFormatter::create( diff --git a/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php b/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php index fb9447ba..946b4892 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php @@ -41,7 +41,7 @@ declare(strict_types=1); namespace App\Services\LabelSystem\PlaceholderProviders; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\UserSystem\User; use App\Entity\Parts\PartLot; use App\Services\Formatters\AmountFormatter; @@ -95,11 +95,11 @@ final class PartLotProvider implements PlaceholderProviderInterface } if ('[[LOCATION]]' === $placeholder) { - return $label_target->getStorageLocation() instanceof Storelocation ? $label_target->getStorageLocation()->getName() : ''; + return $label_target->getStorageLocation() instanceof StorageLocation ? $label_target->getStorageLocation()->getName() : ''; } if ('[[LOCATION_FULL]]' === $placeholder) { - return $label_target->getStorageLocation() instanceof Storelocation ? $label_target->getStorageLocation()->getFullPath() : ''; + return $label_target->getStorageLocation() instanceof StorageLocation ? $label_target->getStorageLocation()->getFullPath() : ''; } if ('[[OWNER]]' === $placeholder) { diff --git a/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php b/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php index 0df4d3d7..7d9e4db5 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php @@ -46,7 +46,9 @@ use App\Entity\Parts\Manufacturer; 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 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ 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 @@ final class PartProvider implements PlaceholderProviderInterface 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/PlaceholderProviders/StorelocationProvider.php b/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php index 95654eca..4b4d8dcd 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php @@ -23,13 +23,13 @@ declare(strict_types=1); namespace App\Services\LabelSystem\PlaceholderProviders; use App\Entity\UserSystem\User; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; class StorelocationProvider implements PlaceholderProviderInterface { public function replace(string $placeholder, object $label_target, array $options = []): ?string { - if ($label_target instanceof Storelocation) { + if ($label_target instanceof StorageLocation) { if ('[[OWNER]]' === $placeholder) { return $label_target->getOwner() instanceof User ? $label_target->getOwner()->getFullName() : ''; } diff --git a/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php index ca8088da..f37f5901 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php @@ -52,7 +52,7 @@ final class StructuralDBElementProvider implements PlaceholderProviderInterface return $label_target->getComment(); } if ('[[COMMENT_T]]' === $placeholder) { - return strip_tags($label_target->getComment()); + return strip_tags((string) $label_target->getComment()); } if ('[[FULL_PATH]]' === $placeholder) { return $label_target->getFullPath(); diff --git a/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php index 8588e133..b316abf2 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php @@ -42,7 +42,6 @@ declare(strict_types=1); namespace App\Services\LabelSystem\PlaceholderProviders; use App\Entity\Contracts\TimeStampableInterface; -use DateTime; use IntlDateFormatter; use Locale; @@ -57,11 +56,11 @@ final class TimestampableElementProvider implements PlaceholderProviderInterface $formatter = new IntlDateFormatter(Locale::getDefault(), IntlDateFormatter::SHORT, IntlDateFormatter::SHORT); if ('[[LAST_MODIFIED]]' === $placeholder) { - return $formatter->format($label_target->getLastModified() ?? new DateTime()); + return $formatter->format($label_target->getLastModified() ?? new \DateTimeImmutable()); } if ('[[CREATION_DATE]]' === $placeholder) { - return $formatter->format($label_target->getAddedDate() ?? new DateTime()); + return $formatter->format($label_target->getAddedDate() ?? new \DateTimeImmutable()); } } diff --git a/src/Services/LabelSystem/SandboxedTwigProvider.php b/src/Services/LabelSystem/SandboxedTwigFactory.php similarity index 60% rename from src/Services/LabelSystem/SandboxedTwigProvider.php rename to src/Services/LabelSystem/SandboxedTwigFactory.php index a0426cd1..d5e09fa5 100644 --- a/src/Services/LabelSystem/SandboxedTwigProvider.php +++ b/src/Services/LabelSystem/SandboxedTwigFactory.php @@ -51,78 +51,121 @@ use App\Entity\Contracts\TimeStampableInterface; use App\Entity\LabelSystem\LabelOptions; use App\Entity\LabelSystem\LabelProcessMode; use App\Entity\Parameters\AbstractParameter; +use App\Entity\Parts\InfoProviderReference; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; +use App\Entity\Parts\PartAssociation; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Pricedetail; use App\Entity\UserSystem\User; +use App\Twig\BarcodeExtension; +use App\Twig\EntityExtension; use App\Twig\FormatExtension; use App\Twig\Sandbox\InheritanceSecurityPolicy; +use App\Twig\Sandbox\SandboxedLabelExtension; +use App\Twig\TwigCoreExtension; use InvalidArgumentException; use Twig\Environment; use Twig\Extension\SandboxExtension; +use Twig\Extra\Html\HtmlExtension; use Twig\Extra\Intl\IntlExtension; +use Twig\Extra\Markdown\MarkdownExtension; +use Twig\Extra\String\StringExtension; use Twig\Loader\ArrayLoader; use Twig\Sandbox\SecurityPolicyInterface; /** - * @see \App\Tests\Services\LabelSystem\SandboxedTwigProviderTest + * This service creates a sandboxed twig environment for the label system. + * @see \App\Tests\Services\LabelSystem\SandboxedTwigFactoryTest */ -final class SandboxedTwigProvider +final class SandboxedTwigFactory { private const ALLOWED_TAGS = ['apply', 'autoescape', 'do', 'for', 'if', 'set', 'verbatim', 'with']; private const ALLOWED_FILTERS = ['abs', 'batch', 'capitalize', 'column', 'country_name', - 'currency_name', 'currency_symbol', 'date', 'date_modify', 'default', 'escape', 'filter', 'first', 'format', - 'format_currency', 'format_date', 'format_datetime', 'format_number', 'format_time', 'join', 'keys', - 'language_name', 'last', 'length', 'locale_name', 'lower', 'map', 'merge', 'nl2br', 'raw', 'number_format', - 'reduce', 'replace', 'reverse', 'slice', 'sort', 'spaceless', 'split', 'striptags', 'timezone_name', 'title', - 'trim', 'upper', 'url_encode', - //Part-DB specific filters: - 'moneyFormat', 'siFormat', 'amountFormat', ]; + 'currency_name', 'currency_symbol', 'date', 'date_modify', 'data_uri', 'default', 'escape', 'filter', 'first', 'format', + 'format_currency', 'format_date', 'format_datetime', 'format_number', 'format_time', 'html_to_markdown', 'join', 'keys', + 'language_name', 'last', 'length', 'locale_name', 'lower', 'map', 'markdown_to_html', 'merge', 'nl2br', 'raw', 'number_format', + 'reduce', 'replace', 'reverse', 'round', 'slice', 'slug', 'sort', 'spaceless', 'split', 'striptags', 'timezone_name', 'title', + 'trim', 'u', 'upper', 'url_encode', - private const ALLOWED_FUNCTIONS = ['date', 'html_classes', 'max', 'min', 'random', 'range']; + //Part-DB specific filters: + + //FormatExtension: + 'format_money', 'format_si', 'format_amount', 'format_bytes', + + //SandboxedLabelExtension + 'placeholders', + ]; + + private const ALLOWED_FUNCTIONS = ['country_names', 'country_timezones', 'currency_names', 'cycle', + 'date', 'html_classes', 'language_names', 'locale_names', 'max', 'min', 'random', 'range', 'script_names', + 'template_from_string', 'timezone_names', + + //Part-DB specific extensions: + //EntityExtension: + 'entity_type', 'entity_url', + //BarcodeExtension: + 'barcode_svg', + //SandboxedLabelExtension + 'placeholder', + ]; private const ALLOWED_METHODS = [ NamedElementInterface::class => ['getName'], AbstractDBElement::class => ['getID', '__toString'], TimeStampableInterface::class => ['getLastModified', 'getAddedDate'], AbstractStructuralDBElement::class => ['isChildOf', 'isRoot', 'getParent', 'getComment', 'getLevel', - 'getFullPath', 'getPathArray', 'getChildren', 'isNotSelectable', ], - AbstractCompany::class => ['getAddress', 'getPhoneNumber', 'getFaxNumber', 'getEmailAddress', 'getWebsite'], + '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', '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'], PartLot::class => ['isExpired', 'getDescription', 'getComment', 'getExpirationDate', 'getStorageLocation', - 'getPart', 'isInstockUnknown', 'getAmount', 'getNeedsRefill', ], - Storelocation::class => ['isFull', 'isOnlySinglePart', 'isLimitToExistingParts', 'getStorageType'], + 'getPart', 'isInstockUnknown', 'getAmount', 'getNeedsRefill', 'getVendorBarcode'], + StorageLocation::class => ['isFull', 'isOnlySinglePart', 'isLimitToExistingParts', 'getStorageType'], Supplier::class => ['getShippingCosts', 'getDefaultCurrency'], - Part::class => ['isNeedsReview', 'getTags', 'getMass', 'getDescription', 'isFavorite', 'getCategory', - 'getFootprint', 'getPartLots', 'getPartUnit', 'useFloatAmount', 'getMinAmount', 'getAmountSum', + Part::class => ['isNeedsReview', 'getTags', 'getMass', 'getIpn', 'getProviderReference', + 'getDescription', 'getComment', 'isFavorite', 'getCategory', 'getFootprint', + 'getPartLots', 'getPartUnit', 'getPartCustomState', 'useFloatAmount', 'getMinAmount', 'getAmountSum', 'isNotEnoughInstock', 'isAmountUnknown', 'getExpiredAmountSum', 'getManufacturerProductUrl', 'getCustomProductURL', 'getManufacturingStatus', 'getManufacturer', - 'getManufacturerProductNumber', 'getOrderdetails', 'isObsolete', ], + 'getManufacturerProductNumber', 'getOrderdetails', 'isObsolete', + 'getParameters', 'getGroupedParameters', + 'isProjectBuildPart', 'getBuiltProject', + 'getAssociatedPartsAsOwner', 'getAssociatedPartsAsOther', 'getAssociatedPartsAll', + 'getEdaInfo' + ], Currency::class => ['getIsoCode', 'getInverseExchangeRate', 'getExchangeRate'], Orderdetail::class => ['getPart', 'getSupplier', 'getSupplierPartNr', 'getObsolete', - 'getPricedetails', 'findPriceForQty', ], + 'getPricedetails', 'findPriceForQty', 'isObsolete', 'getSupplierProductUrl'], Pricedetail::class => ['getOrderdetail', 'getPrice', 'getPricePerUnit', 'getPriceRelatedQuantity', - 'getMinDiscountQuantity', 'getCurrency', ], + 'getMinDiscountQuantity', 'getCurrency', 'getCurrencyISOCode'], + InfoProviderReference:: class => ['getProviderKey', 'getProviderId', 'getProviderUrl', 'getLastUpdated', 'isProviderCreated'], + PartAssociation::class => ['getType', 'getComment', 'getOwner', 'getOther', 'getOtherType'], + //Only allow very little information about users... User::class => ['isAnonymousUser', 'getUsername', 'getFullName', 'getFirstName', 'getLastName', 'getDepartment', 'getEmail', ], ]; private const ALLOWED_PROPERTIES = []; - public function __construct(private readonly FormatExtension $appExtension) + public function __construct( + private readonly FormatExtension $formatExtension, + private readonly BarcodeExtension $barcodeExtension, + private readonly EntityExtension $entityExtension, + private readonly TwigCoreExtension $twigCoreExtension, + private readonly SandboxedLabelExtension $sandboxedLabelExtension, + ) { } - public function getTwig(LabelOptions $options): Environment + public function createTwig(LabelOptions $options): Environment { if (LabelProcessMode::TWIG !== $options->getProcessMode()) { throw new InvalidArgumentException('The LabelOptions must explicitly allow twig via lines_mode = "twig"!'); @@ -139,9 +182,16 @@ final class SandboxedTwigProvider //Add IntlExtension $twig->addExtension(new IntlExtension()); + $twig->addExtension(new MarkdownExtension()); + $twig->addExtension(new StringExtension()); + $twig->addExtension(new HtmlExtension()); //Add Part-DB specific extension - $twig->addExtension($this->appExtension); + $twig->addExtension($this->formatExtension); + $twig->addExtension($this->barcodeExtension); + $twig->addExtension($this->entityExtension); + $twig->addExtension($this->twigCoreExtension); + $twig->addExtension($this->sandboxedLabelExtension); return $twig; } diff --git a/src/Services/LogSystem/EventCommentNeededHelper.php b/src/Services/LogSystem/EventCommentNeededHelper.php index 8440f199..cacf525f 100644 --- a/src/Services/LogSystem/EventCommentNeededHelper.php +++ b/src/Services/LogSystem/EventCommentNeededHelper.php @@ -22,37 +22,25 @@ declare(strict_types=1); */ 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 00000000..d68c03b2 --- /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/LogSystem/EventUndoHelper.php b/src/Services/LogSystem/EventUndoHelper.php index ed3bc363..c57f7724 100644 --- a/src/Services/LogSystem/EventUndoHelper.php +++ b/src/Services/LogSystem/EventUndoHelper.php @@ -42,7 +42,6 @@ declare(strict_types=1); namespace App\Services\LogSystem; use App\Entity\LogSystem\AbstractLogEntry; -use InvalidArgumentException; class EventUndoHelper { diff --git a/src/Services/LogSystem/EventUndoMode.php b/src/Services/LogSystem/EventUndoMode.php index 51ad664e..de30dcfd 100644 --- a/src/Services/LogSystem/EventUndoMode.php +++ b/src/Services/LogSystem/EventUndoMode.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\LogSystem; use InvalidArgumentException; diff --git a/src/Services/LogSystem/HistoryHelper.php b/src/Services/LogSystem/HistoryHelper.php index f4752b6f..3a31f127 100644 --- a/src/Services/LogSystem/HistoryHelper.php +++ b/src/Services/LogSystem/HistoryHelper.php @@ -44,7 +44,6 @@ namespace App\Services\LogSystem; use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractStructuralDBElement; -use App\Entity\Parameters\AbstractParameter; use App\Entity\Parts\Part; use App\Entity\ProjectSystem\Project; diff --git a/src/Services/LogSystem/LogDataFormatter.php b/src/Services/LogSystem/LogDataFormatter.php index f15fcdc6..af54c60c 100644 --- a/src/Services/LogSystem/LogDataFormatter.php +++ b/src/Services/LogSystem/LogDataFormatter.php @@ -41,7 +41,7 @@ class LogDataFormatter public function formatData(mixed $data, AbstractLogEntry $logEntry, string $fieldName): string { if (is_string($data)) { - $tmp = '"' . mb_strimwidth(htmlspecialchars($data), 0, self::STRING_MAX_LENGTH, ) . '"'; + $tmp = '"' . mb_strimwidth(htmlspecialchars($data), 0, self::STRING_MAX_LENGTH) . '"'; //Show special characters and line breaks $tmp = preg_replace('/\n/', '\\n
    ', $tmp); @@ -87,7 +87,7 @@ class LogDataFormatter private function formatJSON(array $data): string { - $json = htmlspecialchars(json_encode($data, JSON_PRETTY_PRINT), ENT_QUOTES | ENT_SUBSTITUTE); + $json = htmlspecialchars(json_encode($data, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT), ENT_QUOTES | ENT_SUBSTITUTE); return sprintf( '
    ', @@ -136,7 +136,7 @@ class LogDataFormatter } try { - $dateTime = new \DateTime($date, new \DateTimeZone($timezone)); + $dateTime = new \DateTimeImmutable($date, new \DateTimeZone($timezone)); } catch (\Exception) { return 'unknown DateTime format'; } diff --git a/src/Services/LogSystem/LogEntryExtraFormatter.php b/src/Services/LogSystem/LogEntryExtraFormatter.php index a1fd4c9b..ae2a5eba 100644 --- a/src/Services/LogSystem/LogEntryExtraFormatter.php +++ b/src/Services/LogSystem/LogEntryExtraFormatter.php @@ -135,7 +135,7 @@ class LogEntryExtraFormatter } if ($context instanceof LogWithCommentInterface && $context->hasComment()) { - $array[] = htmlspecialchars($context->getComment()); + $array[] = htmlspecialchars((string) $context->getComment()); } if ($context instanceof ElementCreatedLogEntry && $context->hasCreationInstockValue()) { @@ -193,6 +193,10 @@ class LogEntryExtraFormatter htmlspecialchars($this->elementTypeNameGenerator->getLocalizedTypeLabel(PartLot::class)) .' ' . $context->getMoveToTargetID(); } + if ($context->getActionTimestamp() !== null) { + $formatter = new \IntlDateFormatter($this->translator->getLocale(), \IntlDateFormatter::SHORT, \IntlDateFormatter::SHORT); + $array['log.part_stock_changed.timestamp'] = $formatter->format($context->getActionTimestamp()); + } } return $array; diff --git a/src/Services/LogSystem/LogLevelHelper.php b/src/Services/LogSystem/LogLevelHelper.php index 5cc13db8..67e87392 100644 --- a/src/Services/LogSystem/LogLevelHelper.php +++ b/src/Services/LogSystem/LogLevelHelper.php @@ -22,7 +22,6 @@ declare(strict_types=1); */ namespace App\Services\LogSystem; -use App\Entity\LogSystem\AbstractLogEntry; use Psr\Log\LogLevel; class LogLevelHelper diff --git a/src/Services/LogSystem/LogTargetHelper.php b/src/Services/LogSystem/LogTargetHelper.php index 0a10a023..5dd649f1 100644 --- a/src/Services/LogSystem/LogTargetHelper.php +++ b/src/Services/LogSystem/LogTargetHelper.php @@ -22,17 +22,9 @@ declare(strict_types=1); */ namespace App\Services\LogSystem; -use App\Entity\Attachments\Attachment; use App\Entity\Base\AbstractDBElement; -use App\Entity\Contracts\NamedElementInterface; use App\Entity\LogSystem\AbstractLogEntry; use App\Entity\LogSystem\UserNotAllowedLogEntry; -use App\Entity\Parameters\AbstractParameter; -use App\Entity\Parts\PartLot; -use App\Entity\PriceInformations\Orderdetail; -use App\Entity\PriceInformations\Pricedetail; -use App\Entity\ProjectSystem\ProjectBOMEntry; -use App\Exceptions\EntityNotSupportedException; use App\Repository\LogEntryRepository; use App\Services\ElementTypeNameGenerator; use App\Services\EntityURLGenerator; diff --git a/src/Services/LogSystem/TimeTravel.php b/src/Services/LogSystem/TimeTravel.php index 400e85f5..68d962bb 100644 --- a/src/Services/LogSystem/TimeTravel.php +++ b/src/Services/LogSystem/TimeTravel.php @@ -34,12 +34,14 @@ use App\Repository\LogEntryRepository; use Brick\Math\BigDecimal; use DateTime; use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Exception; use InvalidArgumentException; use ReflectionClass; +use Symfony\Component\PropertyAccess\PropertyAccessor; class TimeTravel { @@ -54,8 +56,11 @@ class TimeTravel /** * Undeletes the element with the given ID. * + * @template T of AbstractDBElement * @param string $class The class name of the element that should be undeleted + * @phpstan-param class-string $class * @param int $id the ID of the element that should be undeleted + * @phpstan-return T */ public function undeleteEntity(string $class, int $id): AbstractDBElement { @@ -136,16 +141,16 @@ class TimeTravel //Revert many-to-one association (one element in property) if ( - ClassMetadataInfo::MANY_TO_ONE === $mapping['type'] - || ClassMetadataInfo::ONE_TO_ONE === $mapping['type'] + ClassMetadata::MANY_TO_ONE === $mapping['type'] + || ClassMetadata::ONE_TO_ONE === $mapping['type'] ) { $target_element = $this->getField($element, $field); if (null !== $target_element && $element->getLastModified() > $timestamp) { $this->revertEntityToTimestamp($target_element, $timestamp, $reverted_elements); } } elseif ( //Revert *_TO_MANY associations (collection properties) - (ClassMetadataInfo::MANY_TO_MANY === $mapping['type'] - || ClassMetadataInfo::ONE_TO_MANY === $mapping['type']) + (ClassMetadata::MANY_TO_MANY === $mapping['type'] + || ClassMetadata::ONE_TO_MANY === $mapping['type']) && !$mapping['isOwningSide'] ) { $target_elements = $this->getField($element, $field); @@ -171,17 +176,26 @@ class TimeTravel /** * This function decodes the array which is created during the json_encode of a datetime object and returns a DateTime object. * @param array $input - * @return DateTime + * @return \DateTimeInterface * @throws Exception */ - private function dateTimeDecode(?array $input): ?\DateTime + private function dateTimeDecode(?array $input, string $doctrineType): ?\DateTimeInterface { //Allow null values if ($input === null) { return null; } - return new \DateTime($input['date'], new \DateTimeZone($input['timezone'])); + //Mutable types + if (in_array($doctrineType, [Types::DATETIME_MUTABLE, Types::DATE_MUTABLE], true)) { + return new \DateTime($input['date'], new \DateTimeZone($input['timezone'])); + } + //Immutable types + if (in_array($doctrineType, [Types::DATETIME_IMMUTABLE, Types::DATE_IMMUTABLE], true)) { + return new \DateTimeImmutable($input['date'], new \DateTimeZone($input['timezone'])); + } + + throw new InvalidArgumentException('The given doctrine type is not a datetime type!'); } /** @@ -202,14 +216,24 @@ class TimeTravel $old_data = $logEntry->getOldData(); foreach ($old_data as $field => $data) { - if ($metadata->hasField($field)) { + + //We use the fieldMappings property directly instead of the hasField method, as we do not want to match the embedded field itself + //The sub fields are handled in the setField method + if (isset($metadata->fieldMappings[$field])) { //We need to convert the string to a BigDecimal first - if (!$data instanceof BigDecimal && ('big_decimal' === $metadata->getFieldMapping($field)['type'])) { + if (!$data instanceof BigDecimal && ('big_decimal' === $metadata->getFieldMapping($field)->type)) { $data = BigDecimal::of($data); } - if (!$data instanceof DateTime && ('datetime' === $metadata->getFieldMapping($field)['type'])) { - $data = $this->dateTimeDecode($data); + if (!$data instanceof \DateTimeInterface + && (in_array($metadata->getFieldMapping($field)->type, + [ + Types::DATETIME_IMMUTABLE, + Types::DATETIME_IMMUTABLE, + Types::DATE_MUTABLE, + Types::DATETIME_IMMUTABLE + ], true))) { + $data = $this->dateTimeDecode($data, $metadata->getFieldMapping($field)->type); } $this->setField($element, $field, $data); @@ -219,7 +243,7 @@ class TimeTravel $target_class = $mapping['targetEntity']; //Try to extract the old ID: if (is_array($data) && isset($data['@id'])) { - $entity = $this->em->getPartialReference($target_class, $data['@id']); + $entity = $this->em->getReference($target_class, $data['@id']); $this->setField($element, $field, $entity); } } @@ -241,8 +265,22 @@ class TimeTravel */ protected function setField(AbstractDBElement $element, string $field, mixed $new_value): void { - $reflection = new ReflectionClass($element::class); - $property = $reflection->getProperty($field); + //If the field name contains a dot, it is a embeddedable object and we need to split the field name + if (str_contains($field, '.')) { + [$embedded, $embedded_field] = explode('.', $field); + + $elementClass = new ReflectionClass($element::class); + $property = $elementClass->getProperty($embedded); + $embeddedClass = $property->getValue($element); + + $embeddedReflection = new ReflectionClass($embeddedClass::class); + $property = $embeddedReflection->getProperty($embedded_field); + $target_element = $embeddedClass; + } else { + $reflection = new ReflectionClass($element::class); + $property = $reflection->getProperty($field); + $target_element = $element; + } //Check if the property is an BackedEnum, then convert the int or float value to an enum instance if ((is_string($new_value) || is_int($new_value)) @@ -253,6 +291,6 @@ class TimeTravel $new_value = $enum_class::from($new_value); } - $property->setValue($element, $new_value); + $property->setValue($target_element, $new_value); } } diff --git a/src/Services/Misc/ConsoleInfoHelper.php b/src/Services/Misc/ConsoleInfoHelper.php index f529f74a..98de5e07 100644 --- a/src/Services/Misc/ConsoleInfoHelper.php +++ b/src/Services/Misc/ConsoleInfoHelper.php @@ -36,6 +36,7 @@ class ConsoleInfoHelper /** * Returns the username of the user who started the current script if possible. * @return string|null the username of the user who started the current script if possible, null otherwise + * @noinspection PhpUndefinedFunctionInspection */ public function getCLIUser(): ?string { diff --git a/src/Services/Misc/FAIconGenerator.php b/src/Services/Misc/FAIconGenerator.php index 18db1fad..2ea727af 100644 --- a/src/Services/Misc/FAIconGenerator.php +++ b/src/Services/Misc/FAIconGenerator.php @@ -24,7 +24,6 @@ namespace App\Services\Misc; use App\Entity\Attachments\Attachment; use function in_array; -use InvalidArgumentException; /** * @see \App\Tests\Services\Misc\FAIconGeneratorTest diff --git a/src/Services/OAuth/OAuthTokenManager.php b/src/Services/OAuth/OAuthTokenManager.php index 1f6f6b51..9c22503b 100644 --- a/src/Services/OAuth/OAuthTokenManager.php +++ b/src/Services/OAuth/OAuthTokenManager.php @@ -47,11 +47,10 @@ final class OAuthTokenManager $tokenEntity = $this->entityManager->getRepository(OAuthToken::class)->findOneBy(['name' => $app_name]); //If the token was already existing, we just replace it with the new one - if ($tokenEntity) { + if ($tokenEntity !== null) { $tokenEntity->replaceWithNewToken($token); - //@phpstan-ignore-next-line - $this->entityManager->flush($tokenEntity); + $this->entityManager->flush(); //We are done return $tokenEntity; @@ -60,8 +59,8 @@ final class OAuthTokenManager //If the token was not existing, we create a new one $tokenEntity = OAuthToken::fromAccessToken($token, $app_name); $this->entityManager->persist($tokenEntity); - //@phpstan-ignore-next-line - $this->entityManager->flush($tokenEntity); + + $this->entityManager->flush(); return $tokenEntity; } @@ -97,8 +96,8 @@ final class OAuthTokenManager { $token = $this->getToken($app_name); - if (!$token) { - throw new \Exception('No token was saved yet for '.$app_name); + if ($token === null) { + throw new \RuntimeException('No token was saved yet for '.$app_name); } $client = $this->clientRegistry->getClient($app_name); @@ -113,9 +112,7 @@ final class OAuthTokenManager //Persist the token $token->replaceWithNewToken($new_token); - - //@phpstan-ignore-next-line - $this->entityManager->flush($token); + $this->entityManager->flush(); return $token; } @@ -131,7 +128,7 @@ final class OAuthTokenManager $token = $this->getToken($app_name); //If the token is not existing, we return null - if (!$token) { + if ($token === null) { return null; } diff --git a/src/Services/Parameters/ParameterExtractor.php b/src/Services/Parameters/ParameterExtractor.php index 9eaf946a..a133b282 100644 --- a/src/Services/Parameters/ParameterExtractor.php +++ b/src/Services/Parameters/ParameterExtractor.php @@ -88,7 +88,7 @@ class ParameterExtractor protected function stringToParam(string $input, string $class): ?AbstractParameter { $input = trim($input); - $regex = '/^(.*) *(?:=|:) *(.+)/u'; + $regex = '/^(.*) *(?:=|:)(?!\/) *(.+)/u'; $matches = []; preg_match($regex, $input, $matches); diff --git a/src/Services/Parts/PartLotWithdrawAddHelper.php b/src/Services/Parts/PartLotWithdrawAddHelper.php index 8e2d2d28..34ec4c1d 100644 --- a/src/Services/Parts/PartLotWithdrawAddHelper.php +++ b/src/Services/Parts/PartLotWithdrawAddHelper.php @@ -4,18 +4,20 @@ declare(strict_types=1); namespace App\Services\Parts; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\LogSystem\PartStockChangedLogEntry; use App\Entity\Parts\PartLot; use App\Services\LogSystem\EventCommentHelper; use App\Services\LogSystem\EventLogger; +use Doctrine\ORM\EntityManagerInterface; /** * @see \App\Tests\Services\Parts\PartLotWithdrawAddHelperTest */ final class PartLotWithdrawAddHelper { - public function __construct(private readonly EventLogger $eventLogger, private readonly EventCommentHelper $eventCommentHelper) + public function __construct(private readonly EventLogger $eventLogger, + private readonly EventCommentHelper $eventCommentHelper, private readonly EntityManagerInterface $entityManager) { } @@ -30,7 +32,7 @@ final class PartLotWithdrawAddHelper } //So far all other restrictions are defined at the storelocation level - if(!$partLot->getStorageLocation() instanceof Storelocation) { + if(!$partLot->getStorageLocation() instanceof StorageLocation) { return true; } //We can not add parts if the storage location of the lot is marked as full @@ -53,9 +55,10 @@ final class PartLotWithdrawAddHelper * @param PartLot $partLot The partLot from which the instock should be taken (which value should be decreased) * @param float $amount The amount of parts that should be taken from the part lot * @param string|null $comment The optional comment describing the reason for the withdrawal - * @return PartLot The modified part lot + * @param \DateTimeInterface|null $action_timestamp The optional timestamp, where the action happened. Useful if the action happened in the past, and the log entry is created afterwards. + * @param bool $delete_lot_if_empty If true, the part lot will be deleted if the amount is 0 after the withdrawal. */ - public function withdraw(PartLot $partLot, float $amount, ?string $comment = null): PartLot + public function withdraw(PartLot $partLot, float $amount, ?string $comment = null, ?\DateTimeInterface $action_timestamp = null, bool $delete_lot_if_empty = false): void { //Ensure that amount is positive if ($amount <= 0) { @@ -83,7 +86,7 @@ final class PartLotWithdrawAddHelper $oldAmount = $partLot->getAmount(); $partLot->setAmount($oldAmount - $amount); - $event = PartStockChangedLogEntry::withdraw($partLot, $oldAmount, $partLot->getAmount(), $part->getAmountSum() , $comment); + $event = PartStockChangedLogEntry::withdraw($partLot, $oldAmount, $partLot->getAmount(), $part->getAmountSum() , $comment, $action_timestamp); $this->eventLogger->log($event); //Apply the comment also to global events, so it gets associated with the elementChanged log entry @@ -91,7 +94,9 @@ final class PartLotWithdrawAddHelper $this->eventCommentHelper->setMessage($comment); } - return $partLot; + if ($delete_lot_if_empty && $partLot->getAmount() === 0.0) { + $this->entityManager->remove($partLot); + } } /** @@ -100,9 +105,10 @@ final class PartLotWithdrawAddHelper * @param PartLot $partLot The partLot from which the instock should be taken (which value should be decreased) * @param float $amount The amount of parts that should be taken from the part lot * @param string|null $comment The optional comment describing the reason for the withdrawal + * @param \DateTimeInterface|null $action_timestamp The optional timestamp, where the action happened. Useful if the action happened in the past, and the log entry is created afterwards. * @return PartLot The modified part lot */ - public function add(PartLot $partLot, float $amount, ?string $comment = null): PartLot + public function add(PartLot $partLot, float $amount, ?string $comment = null, ?\DateTimeInterface $action_timestamp = null): PartLot { if ($amount <= 0) { throw new \InvalidArgumentException('Amount must be positive'); @@ -123,7 +129,7 @@ final class PartLotWithdrawAddHelper $oldAmount = $partLot->getAmount(); $partLot->setAmount($oldAmount + $amount); - $event = PartStockChangedLogEntry::add($partLot, $oldAmount, $partLot->getAmount(), $part->getAmountSum() , $comment); + $event = PartStockChangedLogEntry::add($partLot, $oldAmount, $partLot->getAmount(), $part->getAmountSum() , $comment, $action_timestamp); $this->eventLogger->log($event); //Apply the comment also to global events, so it gets associated with the elementChanged log entry @@ -141,8 +147,10 @@ final class PartLotWithdrawAddHelper * @param PartLot $target The part lot to which the parts should be added * @param float $amount The amount of parts that should be moved * @param string|null $comment A comment describing the reason for the move + * @param \DateTimeInterface|null $action_timestamp The optional timestamp, where the action happened. Useful if the action happened in the past, and the log entry is created afterwards. + * @param bool $delete_lot_if_empty If true, the part lot will be deleted if the amount is 0 after the withdrawal. */ - public function move(PartLot $origin, PartLot $target, float $amount, ?string $comment = null): void + public function move(PartLot $origin, PartLot $target, float $amount, ?string $comment = null, ?\DateTimeInterface $action_timestamp = null, bool $delete_lot_if_empty = false): void { if ($amount <= 0) { throw new \InvalidArgumentException('Amount must be positive'); @@ -177,12 +185,16 @@ final class PartLotWithdrawAddHelper //And add it to the target $target->setAmount($target->getAmount() + $amount); - $event = PartStockChangedLogEntry::move($origin, $oldOriginAmount, $origin->getAmount(), $part->getAmountSum() , $comment, $target); + $event = PartStockChangedLogEntry::move($origin, $oldOriginAmount, $origin->getAmount(), $part->getAmountSum() , $comment, $target, $action_timestamp); $this->eventLogger->log($event); //Apply the comment also to global events, so it gets associated with the elementChanged log entry if (!$this->eventCommentHelper->isMessageSet() && ($comment !== null && $comment !== '')) { $this->eventCommentHelper->setMessage($comment); } + + if ($delete_lot_if_empty && $origin->getAmount() === 0.0) { + $this->entityManager->remove($origin); + } } } diff --git a/src/Services/Parts/PartsTableActionHandler.php b/src/Services/Parts/PartsTableActionHandler.php index 08c0715d..945cff7b 100644 --- a/src/Services/Parts/PartsTableActionHandler.php +++ b/src/Services/Parts/PartsTableActionHandler.php @@ -22,6 +22,7 @@ declare(strict_types=1); */ 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,14 +30,14 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Repository\DBElementRepository; -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) @@ -54,7 +55,6 @@ final class PartsTableActionHandler { $id_array = explode(',', $ids); - /** @var PartRepository $repo */ $repo = $this->entityManager->getRepository(Part::class); return $repo->getElementsFromIDArray($id_array); @@ -63,8 +63,9 @@ final class PartsTableActionHandler /** * @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( @@ -97,7 +98,7 @@ implode(',', array_map(static fn (PartLot $lot) => $lot->getID(), $part->getPart //When action starts with "export_" we have to redirect to the export controller $matches = []; - if (preg_match('/^export_(json|yaml|xml|csv)$/', $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', @@ -116,6 +117,16 @@ implode(',', array_map(static fn (PartLot $lot) => $lot->getID(), $part->getPart ); } + if ($action === 'bulk_info_provider_import') { + $ids = implode(',', array_map(static fn (Part $part) => $part->getID(), $selected_parts)); + return new RedirectResponse( + $this->urlGenerator->generate('bulk_info_provider_step1', [ + 'ids' => $ids, + '_redirect' => $redirect_url + ]) + ); + } + //Iterate over the parts and apply the action to it: foreach ($selected_parts as $part) { @@ -163,6 +174,29 @@ implode(',', array_map(static fn (PartLot $lot) => $lot->getID(), $part->getPart $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 092cc278..b2e1340f 100644 --- a/src/Services/Parts/PricedetailHelper.php +++ b/src/Services/Parts/PricedetailHelper.php @@ -25,6 +25,7 @@ namespace App\Services\Parts; 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 269c7e4c..a541c29d 100644 --- a/src/Services/ProjectSystem/ProjectBuildHelper.php +++ b/src/Services/ProjectSystem/ProjectBuildHelper.php @@ -31,9 +31,9 @@ use App\Services\Parts\PartLotWithdrawAddHelper; /** * @see \App\Tests\Services\ProjectSystem\ProjectBuildHelperTest */ -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 @@ class ProjectBuildHelper */ public function getMaximumBuildableCount(Project $project): int { + $bom_entries = $project->getBomEntries(); + if ($bom_entries->isEmpty()) { + return 0; + } $maximum_buildable_count = PHP_INT_MAX; - foreach ($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 new file mode 100644 index 00000000..bb27158f --- /dev/null +++ b/src/Services/System/BannerHelper.php @@ -0,0 +1,46 @@ +. + */ + +declare(strict_types=1); + + +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 CustomizationSettings $customizationSettings) + { + + } + + /** + * Retrieves the banner from either the env variable or the banner.md file. + * @return string + */ + public function getBanner(): string + { + return $this->customizationSettings->banner ?? ""; + } +} diff --git a/src/Services/System/UpdateAvailableManager.php b/src/Services/System/UpdateAvailableManager.php index 31cb3266..82cfb84e 100644 --- a/src/Services/System/UpdateAvailableManager.php +++ b/src/Services/System/UpdateAvailableManager.php @@ -23,6 +23,7 @@ declare(strict_types=1); 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 @@ class UpdateAvailableManager 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 @@ class UpdateAvailableManager 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 eac6de16..6eb7ec13 100644 --- a/src/Services/Tools/ExchangeRateUpdater.php +++ b/src/Services/Tools/ExchangeRateUpdater.php @@ -23,13 +23,16 @@ declare(strict_types=1); 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 @@ class ExchangeRateUpdater */ 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 + 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()); - $rate_inverted = BigDecimal::one()->dividedBy($rate_bd, Currency::PRICE_SCALE, RoundingMode::HALF_UP); + $rate_bd = BigDecimal::of($rate->getValue()); + $effective_rate = BigDecimal::one()->dividedBy($rate_bd, Currency::PRICE_SCALE, RoundingMode::HALF_UP); + } - $currency->setExchangeRate($rate_inverted); + $currency->setExchangeRate($effective_rate); return $currency; } diff --git a/src/Services/Tools/StatisticsHelper.php b/src/Services/Tools/StatisticsHelper.php index 0ee736f9..00bb05c9 100644 --- a/src/Services/Tools/StatisticsHelper.php +++ b/src/Services/Tools/StatisticsHelper.php @@ -49,7 +49,7 @@ use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Repository\AttachmentRepository; @@ -113,7 +113,7 @@ class StatisticsHelper 'footprint' => Footprint::class, 'manufacturer' => Manufacturer::class, 'measurement_unit' => MeasurementUnit::class, - 'storelocation' => Storelocation::class, + 'storelocation' => StorageLocation::class, 'supplier' => Supplier::class, 'currency' => Currency::class, ]; @@ -122,7 +122,6 @@ class StatisticsHelper throw new InvalidArgumentException('No count for the given type available!'); } - /** @var EntityRepository $repo */ $repo = $this->em->getRepository($arr[$type]); return $repo->count([]); diff --git a/src/Services/Tools/TagFinder.php b/src/Services/Tools/TagFinder.php index bfc7c9db..80c89e0f 100644 --- a/src/Services/Tools/TagFinder.php +++ b/src/Services/Tools/TagFinder.php @@ -66,7 +66,7 @@ class TagFinder $qb->select('p.tags') ->from(Part::class, 'p') - ->where('p.tags LIKE ?1') + ->where('ILIKE(p.tags, ?1) = TRUE') ->setMaxResults($options['query_limit']) //->orderBy('RAND()') ->setParameter(1, '%'.$keyword.'%'); diff --git a/src/Services/Trees/NodesListBuilder.php b/src/Services/Trees/NodesListBuilder.php index 66299185..e65fa37e 100644 --- a/src/Services/Trees/NodesListBuilder.php +++ b/src/Services/Trees/NodesListBuilder.php @@ -22,14 +22,15 @@ declare(strict_types=1); namespace App\Services\Trees; -use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractStructuralDBElement; use App\Repository\AttachmentContainingDBElementRepository; use App\Repository\DBElementRepository; +use App\Repository\NamedDBElementRepository; use App\Repository\StructuralDBElementRepository; -use App\Services\UserSystem\UserCacheKeyGenerator; +use App\Services\Cache\ElementCacheTagGenerator; +use App\Services\Cache\UserCacheKeyGenerator; use Doctrine\ORM\EntityManagerInterface; use Symfony\Contracts\Cache\ItemInterface; use Symfony\Contracts\Cache\TagAwareCacheInterface; @@ -40,19 +41,23 @@ use Symfony\Contracts\Cache\TagAwareCacheInterface; */ class NodesListBuilder { - public function __construct(protected EntityManagerInterface $em, protected TagAwareCacheInterface $cache, protected UserCacheKeyGenerator $keyGenerator) - { + public function __construct( + protected EntityManagerInterface $em, + protected TagAwareCacheInterface $cache, + protected UserCacheKeyGenerator $keyGenerator, + protected ElementCacheTagGenerator $tagGenerator, + ) { } /** * Gets a flattened hierarchical tree. Useful for generating option lists. * In difference to the Repository Function, the results here are cached. * - * @template T of AbstractDBElement + * @template T of AbstractNamedDBElement * - * @param string $class_name the class name of the entity you want to retrieve + * @param string $class_name the class name of the entity you want to retrieve * @phpstan-param class-string $class_name - * @param AbstractStructuralDBElement|null $parent This entity will be used as root element. Set to null, to use global root + * @param AbstractStructuralDBElement|null $parent This entity will be used as root element. Set to null, to use global root * * @return AbstractDBElement[] a flattened list containing the tree elements * @phpstan-return list @@ -66,7 +71,7 @@ class NodesListBuilder $ids = $this->getFlattenedIDs($class_name, $parent); //Retrieve the elements from the IDs, the order is the same as in the $ids array - /** @var DBElementRepository $repo */ + /** @var NamedDBElementRepository $repo */ $repo = $this->em->getRepository($class_name); if ($repo instanceof AttachmentContainingDBElementRepository) { @@ -78,7 +83,9 @@ class NodesListBuilder /** * This functions returns the (cached) list of the IDs of the elements for the flattened tree. + * @template T of AbstractNamedDBElement * @param string $class_name + * @phpstan-param class-string $class_name * @param AbstractStructuralDBElement|null $parent * @return int[] */ @@ -86,17 +93,19 @@ class NodesListBuilder { $parent_id = $parent instanceof AbstractStructuralDBElement ? $parent->getID() : '0'; // Backslashes are not allowed in cache keys - $secure_class_name = str_replace('\\', '_', $class_name); + $secure_class_name = $this->tagGenerator->getElementTypeCacheTag($class_name); $key = 'list_'.$this->keyGenerator->generateKey().'_'.$secure_class_name.$parent_id; return $this->cache->get($key, function (ItemInterface $item) use ($class_name, $parent, $secure_class_name) { // Invalidate when groups, an element with the class or the user changes $item->tag(['groups', 'tree_list', $this->keyGenerator->generateKey(), $secure_class_name]); - /** @var StructuralDBElementRepository $repo */ + /** @var NamedDBElementRepository $repo */ $repo = $this->em->getRepository($class_name); - return array_map(fn(AbstractDBElement $element) => $element->getID(), $repo->getFlatList($parent)); + return array_map(static fn(AbstractDBElement $element) => $element->getID(), + //@phpstan-ignore-next-line For some reason phpstan does not understand that $repo is a StructuralDBElementRepository + $repo->getFlatList($parent)); }); } @@ -105,7 +114,7 @@ class NodesListBuilder * The value is cached for performance reasons. * * @template T of AbstractStructuralDBElement - * @param T $element + * @param T $element * @return AbstractStructuralDBElement[] * * @phpstan-return list diff --git a/src/Services/Trees/SidebarTreeUpdater.php b/src/Services/Trees/SidebarTreeUpdater.php index 0d7ccee9..c0f93b1f 100644 --- a/src/Services/Trees/SidebarTreeUpdater.php +++ b/src/Services/Trees/SidebarTreeUpdater.php @@ -22,7 +22,6 @@ declare(strict_types=1); */ namespace App\Services\Trees; -use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\ItemInterface; use Symfony\Contracts\Cache\TagAwareCacheInterface; @@ -50,7 +49,7 @@ final class SidebarTreeUpdater //This tag and therfore this whole cache gets cleared by TreeCacheInvalidationListener when a structural element is changed $item->tag('sidebar_tree_update'); - return new \DateTime(); + return new \DateTimeImmutable(); }); } } diff --git a/src/Services/Trees/ToolsTreeBuilder.php b/src/Services/Trees/ToolsTreeBuilder.php index b0fafb4f..37a09b09 100644 --- a/src/Services/Trees/ToolsTreeBuilder.php +++ b/src/Services/Trees/ToolsTreeBuilder.php @@ -22,22 +22,24 @@ declare(strict_types=1); namespace App\Services\Trees; -use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Attachments\AttachmentType; -use App\Entity\ProjectSystem\Project; use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\PartCustomState; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; +use App\Entity\ProjectSystem\Project; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use App\Helpers\Trees\TreeViewNode; -use App\Services\UserSystem\UserCacheKeyGenerator; +use App\Services\Cache\UserCacheKeyGenerator; +use App\Services\ElementTypeNameGenerator; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Contracts\Cache\ItemInterface; use Symfony\Contracts\Cache\TagAwareCacheInterface; @@ -49,8 +51,14 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class ToolsTreeBuilder { - public function __construct(protected TranslatorInterface $translator, protected UrlGeneratorInterface $urlGenerator, protected TagAwareCacheInterface $cache, protected UserCacheKeyGenerator $keyGenerator, protected Security $security) - { + public function __construct( + protected TranslatorInterface $translator, + protected UrlGeneratorInterface $urlGenerator, + protected TagAwareCacheInterface $cache, + protected UserCacheKeyGenerator $keyGenerator, + protected Security $security, + private readonly ElementTypeNameGenerator $elementTypeNameGenerator, + ) { } /** @@ -138,6 +146,11 @@ class ToolsTreeBuilder $this->translator->trans('info_providers.search.title'), $this->urlGenerator->generate('info_providers_search') ))->setIcon('fa-treeview fa-fw fa-solid fa-cloud-arrow-down'); + + $nodes[] = (new TreeViewNode( + $this->translator->trans('info_providers.bulk_import.manage_jobs'), + $this->urlGenerator->generate('bulk_info_provider_manage') + ))->setIcon('fa-treeview fa-fw fa-solid fa-tasks'); } return $nodes; @@ -154,64 +167,70 @@ class ToolsTreeBuilder if ($this->security->isGranted('read', new AttachmentType())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.attachment_types'), + $this->elementTypeNameGenerator->typeLabelPlural(AttachmentType::class), $this->urlGenerator->generate('attachment_type_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-file-alt'); } if ($this->security->isGranted('read', new Category())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.categories'), + $this->elementTypeNameGenerator->typeLabelPlural(Category::class), $this->urlGenerator->generate('category_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-tags'); } if ($this->security->isGranted('read', new Project())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.projects'), + $this->elementTypeNameGenerator->typeLabelPlural(Project::class), $this->urlGenerator->generate('project_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-archive'); } if ($this->security->isGranted('read', new Supplier())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.suppliers'), + $this->elementTypeNameGenerator->typeLabelPlural(Supplier::class), $this->urlGenerator->generate('supplier_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-truck'); } if ($this->security->isGranted('read', new Manufacturer())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.manufacturer'), + $this->elementTypeNameGenerator->typeLabelPlural(Manufacturer::class), $this->urlGenerator->generate('manufacturer_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-industry'); } - if ($this->security->isGranted('read', new Storelocation())) { + if ($this->security->isGranted('read', new StorageLocation())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.storelocation'), + $this->elementTypeNameGenerator->typeLabelPlural(StorageLocation::class), $this->urlGenerator->generate('store_location_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-cube'); } if ($this->security->isGranted('read', new Footprint())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.footprint'), + $this->elementTypeNameGenerator->typeLabelPlural(Footprint::class), $this->urlGenerator->generate('footprint_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-microchip'); } if ($this->security->isGranted('read', new Currency())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.currency'), + $this->elementTypeNameGenerator->typeLabelPlural(Currency::class), $this->urlGenerator->generate('currency_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-coins'); } if ($this->security->isGranted('read', new MeasurementUnit())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.measurement_unit'), + $this->elementTypeNameGenerator->typeLabelPlural(MeasurementUnit::class), $this->urlGenerator->generate('measurement_unit_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-balance-scale'); } if ($this->security->isGranted('read', new LabelProfile())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.label_profile'), + $this->elementTypeNameGenerator->typeLabelPlural(LabelProfile::class), $this->urlGenerator->generate('label_profile_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-qrcode'); } + if ($this->security->isGranted('read', new PartCustomState())) { + $nodes[] = (new TreeViewNode( + $this->elementTypeNameGenerator->typeLabelPlural(PartCustomState::class), + $this->urlGenerator->generate('part_custom_state_new') + ))->setIcon('fa-fw fa-treeview fa-solid fa-tools'); + } if ($this->security->isGranted('create', new Part())) { $nodes[] = (new TreeViewNode( $this->translator->trans('tree.tools.edit.part'), @@ -289,6 +308,13 @@ class ToolsTreeBuilder ))->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 f3b0d4ea..d55c87b7 100644 --- a/src/Services/Trees/TreeViewGenerator.php +++ b/src/Services/Trees/TreeViewGenerator.php @@ -25,17 +25,20 @@ namespace App\Services\Trees; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractStructuralDBElement; -use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; +use App\Entity\ProjectSystem\Project; use App\Helpers\Trees\TreeViewNode; use App\Helpers\Trees\TreeViewNodeIterator; -use App\Repository\StructuralDBElementRepository; +use App\Repository\NamedDBElementRepository; +use App\Services\Cache\ElementCacheTagGenerator; +use App\Services\Cache\UserCacheKeyGenerator; +use App\Services\ElementTypeNameGenerator; use App\Services\EntityURLGenerator; -use App\Services\UserSystem\UserCacheKeyGenerator; +use App\Settings\BehaviorSettings\SidebarSettings; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use RecursiveIteratorIterator; @@ -51,25 +54,77 @@ use function count; */ class TreeViewGenerator { - public function __construct(protected EntityURLGenerator $urlGenerator, protected EntityManagerInterface $em, protected TagAwareCacheInterface $cache, - protected UserCacheKeyGenerator $keyGenerator, protected TranslatorInterface $translator, private UrlGeneratorInterface $router, - protected bool $rootNodeExpandedByDefault, protected bool $rootNodeEnabled) + + private readonly bool $rootNodeExpandedByDefault; + private readonly bool $rootNodeEnabled; + + public function __construct( + protected EntityURLGenerator $urlGenerator, + protected EntityManagerInterface $em, + protected TagAwareCacheInterface $cache, + protected ElementCacheTagGenerator $tagGenerator, + protected UserCacheKeyGenerator $keyGenerator, + protected TranslatorInterface $translator, + private readonly UrlGeneratorInterface $router, + private readonly SidebarSettings $sidebarSettings, + private readonly ElementTypeNameGenerator $elementTypeNameGenerator + ) { + $this->rootNodeEnabled = $this->sidebarSettings->rootNodeEnabled; + $this->rootNodeExpandedByDefault = $this->sidebarSettings->rootNodeExpanded; + } + + /** + * Gets a TreeView list for the entities of the given class. + * The result is cached, if the full tree should be shown and no element should be selected. + * + * @param string $class The class for which the treeView should be generated + * @param AbstractStructuralDBElement|null $parent The root nodes in the tree should have this element as parent (use null, if you want to get all entities) + * @param string $mode The link type that will be generated for the hyperlink section of each node (see EntityURLGenerator for possible values). + * Set to empty string, to disable href field. + * @param AbstractDBElement|null $selectedElement The element that should be selected. If set to null, no element will be selected. + * + * @return TreeViewNode[] an array of TreeViewNode[] elements of the root elements + */ + public function getTreeView( + string $class, + ?AbstractStructuralDBElement $parent = null, + string $mode = 'list_parts', + ?AbstractDBElement $selectedElement = null + ): array { + //If we just want a part of a tree, don't cache it or select a specific element, don't cache it + if ($parent instanceof AbstractStructuralDBElement || $selectedElement instanceof AbstractDBElement) { + return $this->getTreeViewUncached($class, $parent, $mode, $selectedElement); + } + + $secure_class_name = $this->tagGenerator->getElementTypeCacheTag($class); + $key = 'sidebar_treeview_'.$this->keyGenerator->generateKey().'_'.$secure_class_name; + $key .= $mode; + + return $this->cache->get($key, function (ItemInterface $item) use ($class, $parent, $mode, $selectedElement, $secure_class_name) { + // Invalidate when groups, an element with the class or the user changes + $item->tag(['groups', 'tree_treeview', $this->keyGenerator->generateKey(), $secure_class_name]); + return $this->getTreeViewUncached($class, $parent, $mode, $selectedElement); + }); } /** * Gets a TreeView list for the entities of the given class. * - * @param string $class The class for which the treeView should be generated - * @param AbstractStructuralDBElement|null $parent The root nodes in the tree should have this element as parent (use null, if you want to get all entities) - * @param string $mode The link type that will be generated for the hyperlink section of each node (see EntityURLGenerator for possible values). + * @param string $class The class for which the treeView should be generated + * @param AbstractStructuralDBElement|null $parent The root nodes in the tree should have this element as parent (use null, if you want to get all entities) + * @param string $mode The link type that will be generated for the hyperlink section of each node (see EntityURLGenerator for possible values). * Set to empty string, to disable href field. - * @param AbstractDBElement|null $selectedElement The element that should be selected. If set to null, no element will be selected. + * @param AbstractDBElement|null $selectedElement The element that should be selected. If set to null, no element will be selected. * * @return TreeViewNode[] an array of TreeViewNode[] elements of the root elements */ - public function getTreeView(string $class, ?AbstractStructuralDBElement $parent = null, string $mode = 'list_parts', ?AbstractDBElement $selectedElement = null): array - { + private function getTreeViewUncached( + string $class, + ?AbstractStructuralDBElement $parent = null, + string $mode = 'list_parts', + ?AbstractDBElement $selectedElement = null + ): array { $head = []; $href_type = $mode; @@ -110,11 +165,11 @@ class TreeViewGenerator } if ($item->getNodes() !== null && $item->getNodes() !== []) { - $item->addTag((string) count($item->getNodes())); + $item->addTag((string)count($item->getNodes())); } if ($href_type !== '' && null !== $item->getId()) { - $entity = $this->em->getPartialReference($class, $item->getId()); + $entity = $this->em->find($class, $item->getId()); $item->setHref($this->urlGenerator->getURL($entity, $href_type)); } @@ -125,10 +180,7 @@ class TreeViewGenerator } 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)); @@ -138,29 +190,42 @@ class TreeViewGenerator 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) { - Category::class => $this->translator->trans('category.labelp'), - Storelocation::class => $this->translator->trans('storelocation.labelp'), - Footprint::class => $this->translator->trans('footprint.labelp'), - Manufacturer::class => $this->translator->trans('manufacturer.labelp'), - Supplier::class => $this->translator->trans('supplier.labelp'), - Project::class => $this->translator->trans('project.labelp'), - default => $this->translator->trans('tree.root_node.text'), - }; + return $this->elementTypeNameGenerator->typeLabelPlural($class); } protected function entityClassToRootNodeIcon(string $class): ?string { $icon = "fa-fw fa-treeview fa-solid "; return match ($class) { - Category::class => $icon . 'fa-tags', - Storelocation::class => $icon . 'fa-cube', - Footprint::class => $icon . 'fa-microchip', - Manufacturer::class => $icon . 'fa-industry', - Supplier::class => $icon . 'fa-truck', - Project::class => $icon . 'fa-archive', + Category::class => $icon.'fa-tags', + StorageLocation::class => $icon.'fa-cube', + Footprint::class => $icon.'fa-microchip', + Manufacturer::class => $icon.'fa-industry', + Supplier::class => $icon.'fa-truck', + Project::class => $icon.'fa-archive', default => null, }; } @@ -170,8 +235,9 @@ class TreeViewGenerator * Gets a tree of TreeViewNode elements. The root elements has $parent as parent. * The treeview is generic, that means the href are null and ID values are set. * - * @param string $class The class for which the tree should be generated - * @param AbstractStructuralDBElement|null $parent the parent the root elements should have + * @param string $class The class for which the tree should be generated + * @phpstan-param class-string $class + * @param AbstractStructuralDBElement|null $parent the parent the root elements should have * * @return TreeViewNode[] */ @@ -184,22 +250,21 @@ class TreeViewGenerator throw new InvalidArgumentException('$parent must be of the type $class!'); } - /** @var StructuralDBElementRepository $repo */ + /** @var NamedDBElementRepository $repo */ $repo = $this->em->getRepository($class); //If we just want a part of a tree, don't cache it if ($parent instanceof AbstractStructuralDBElement) { - return $repo->getGenericNodeTree($parent); + return $repo->getGenericNodeTree($parent); //@phpstan-ignore-line PHPstan does not seem to recognize, that we have a StructuralDBElementRepository here, which have 1 argument } - $secure_class_name = str_replace('\\', '_', $class); + $secure_class_name = $this->tagGenerator->getElementTypeCacheTag($class); $key = 'treeview_'.$this->keyGenerator->generateKey().'_'.$secure_class_name; return $this->cache->get($key, function (ItemInterface $item) use ($repo, $parent, $secure_class_name) { // Invalidate when groups, an element with the class or the user changes $item->tag(['groups', 'tree_treeview', $this->keyGenerator->generateKey(), $secure_class_name]); - - return $repo->getGenericNodeTree($parent); + return $repo->getGenericNodeTree($parent); //@phpstan-ignore-line }); } } diff --git a/src/Services/UserSystem/PasswordResetManager.php b/src/Services/UserSystem/PasswordResetManager.php index 31dbdf90..1a78cb60 100644 --- a/src/Services/UserSystem/PasswordResetManager.php +++ b/src/Services/UserSystem/PasswordResetManager.php @@ -59,8 +59,7 @@ class PasswordResetManager $user->setPwResetToken($this->passwordEncoder->hash($unencrypted_token)); //Determine the expiration datetime of - $expiration_date = new DateTime(); - $expiration_date->add(date_interval_create_from_date_string('1 day')); + $expiration_date = new \DateTimeImmutable("+1 day"); $user->setPwResetExpires($expiration_date); if ($user->getEmail() !== null && $user->getEmail() !== '') { @@ -105,7 +104,7 @@ class PasswordResetManager } //Check if token is expired yet - if ($user->getPwResetExpires() < new DateTime()) { + if ($user->getPwResetExpires() < new \DateTimeImmutable()) { return false; } @@ -119,7 +118,7 @@ class PasswordResetManager //Remove token $user->setPwResetToken(null); - $user->setPwResetExpires(new DateTime()); + $user->setPwResetExpires(new \DateTimeImmutable()); //Save to DB $this->em->flush(); diff --git a/src/Services/UserSystem/PermissionManager.php b/src/Services/UserSystem/PermissionManager.php index a995dad7..663a7b67 100644 --- a/src/Services/UserSystem/PermissionManager.php +++ b/src/Services/UserSystem/PermissionManager.php @@ -22,7 +22,6 @@ declare(strict_types=1); namespace App\Services\UserSystem; -use App\Entity\Base\AbstractStructuralDBElement; use App\Configuration\PermissionsConfiguration; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; @@ -41,7 +40,7 @@ use Symfony\Component\Yaml\Yaml; */ class PermissionManager { - protected $permission_structure; + protected array $permission_structure; protected string $cache_file; /** @@ -125,6 +124,40 @@ class PermissionManager return null; //The inherited value is never resolved. Should be treated as false, in Voters. } + /** + * Same as inherit(), but it checks if the access token has the required role. + * @param User $user the user for which the operation should be checked + * @param array $roles The roles associated with the authentication token + * @param string $permission the name of the permission for which should be checked + * @param string $operation the name of the operation for which should be checked + * + * @return bool|null true, if the user is allowed to do the operation (ALLOW), false if not (DISALLOW), and null, + * if the value is set to inherit + */ + public function inheritWithAPILevel(User $user, array $roles, string $permission, string $operation): ?bool + { + //Check that the permission/operation combination is valid + if (! $this->isValidOperation($permission, $operation)) { + throw new InvalidArgumentException('The permission/operation combination "'.$permission.'/'.$operation.'" is not valid!'); + } + + //Get what API level we require for the permission/operation + $level_role = $this->permission_structure['perms'][$permission]['operations'][$operation]['apiTokenRole']; + + //When no role was set (or it is null), then the operation is blocked for API access + if (null === $level_role) { + return false; + } + + //Otherwise check if the token has the required role, if not, then the operation is blocked for API access + if (!in_array($level_role, $roles, true)) { + return false; + } + + //If we have the required role, then we can check the permission + return $this->inherit($user, $permission, $operation); + } + /** * Sets the new value for the operation. * @@ -195,12 +228,16 @@ class PermissionManager /** * This functions sets all operations mentioned in the alsoSet value of a permission, so that the structure is always valid. + * This function should be called after every setPermission() call. + * @return bool true if values were changed/corrected, false if not */ - public function ensureCorrectSetOperations(HasPermissionsInterface $user): void + public function ensureCorrectSetOperations(HasPermissionsInterface $user): bool { //If we have changed anything on the permission structure due to the alsoSet value, this becomes true, so we //redo the whole process, to ensure that all alsoSet values are set recursively. + $return_value = false; + do { $anything_changed = false; //Reset the variable for the next iteration @@ -219,12 +256,15 @@ class PermissionManager $this->setPermission($user, $set_perm, $set_op, true); //Mark the change, so we redo the whole process $anything_changed = true; + $return_value = true; } } } } } } while($anything_changed); + + return $return_value; } /** diff --git a/src/Services/UserSystem/PermissionPresetsHelper.php b/src/Services/UserSystem/PermissionPresetsHelper.php index eeb80f61..a3ed01b8 100644 --- a/src/Services/UserSystem/PermissionPresetsHelper.php +++ b/src/Services/UserSystem/PermissionPresetsHelper.php @@ -102,9 +102,13 @@ class PermissionPresetsHelper $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'attachment_types', PermissionData::ALLOW); $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'currencies', PermissionData::ALLOW); $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'measurement_units', PermissionData::ALLOW); + $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'part_custom_states', PermissionData::ALLOW); $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'suppliers', PermissionData::ALLOW); $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'projects', PermissionData::ALLOW); + //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 @@ class PermissionPresetsHelper $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'attachment_types', PermissionData::ALLOW, ['import']); $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'currencies', PermissionData::ALLOW, ['import']); $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'measurement_units', PermissionData::ALLOW, ['import']); + $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'part_custom_states', PermissionData::ALLOW, ['import']); $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'suppliers', PermissionData::ALLOW, ['import']); $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'projects', PermissionData::ALLOW, ['import']); diff --git a/src/Services/UserSystem/TFA/DecoratedGoogleAuthenticator.php b/src/Services/UserSystem/TFA/DecoratedGoogleAuthenticator.php index 562358c5..05e5ed4c 100644 --- a/src/Services/UserSystem/TFA/DecoratedGoogleAuthenticator.php +++ b/src/Services/UserSystem/TFA/DecoratedGoogleAuthenticator.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\UserSystem\TFA; use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface; -use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticatorInterface; use Symfony\Component\DependencyInjection\Attribute\AsDecorator; use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated; @@ -33,8 +34,8 @@ class DecoratedGoogleAuthenticator implements GoogleAuthenticatorInterface public function __construct( #[AutowireDecorated] - private GoogleAuthenticatorInterface $inner, - private RequestStack $requestStack) + private readonly GoogleAuthenticatorInterface $inner, + private readonly RequestStack $requestStack) { } @@ -68,4 +69,4 @@ class DecoratedGoogleAuthenticator implements GoogleAuthenticatorInterface { return $this->inner->generateSecret(); } -} \ No newline at end of file +} diff --git a/src/Services/UserSystem/UserAvatarHelper.php b/src/Services/UserSystem/UserAvatarHelper.php index dd06ce1b..9dbe9c12 100644 --- a/src/Services/UserSystem/UserAvatarHelper.php +++ b/src/Services/UserSystem/UserAvatarHelper.php @@ -20,24 +20,35 @@ declare(strict_types=1); * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + namespace App\Services\UserSystem; -use Imagine\Exception\RuntimeException; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentType; +use App\Entity\Attachments\AttachmentUpload; use App\Entity\Attachments\UserAttachment; 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 Liip\ImagineBundle\Service\FilterService; use Symfony\Component\Asset\Packages; use Symfony\Component\HttpFoundation\File\UploadedFile; class UserAvatarHelper { - public function __construct(private readonly bool $use_gravatar, private readonly Packages $packages, private readonly AttachmentURLGenerator $attachmentURLGenerator, private readonly FilterService $filterService, private readonly EntityManagerInterface $entityManager, private readonly AttachmentSubmitHandler $submitHandler) - { + /** + * Path to the default avatar image (must not start with a slash, or the asset package will not work) + */ + public const IMG_DEFAULT_AVATAR_PATH = 'img/default_avatar.svg'; + + public function __construct( + private readonly PrivacySettings $privacySettings, + private readonly Packages $packages, + private readonly AttachmentURLGenerator $attachmentURLGenerator, + private readonly EntityManagerInterface $entityManager, + private readonly AttachmentSubmitHandler $submitHandler + ) { } @@ -51,16 +62,16 @@ class UserAvatarHelper //Check if the user has a master attachment defined (meaning he has explicitly defined a profile picture) if ($user->getMasterPictureAttachment() instanceof Attachment) { return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_md') - ?? throw new RuntimeException('Could not generate thumbnail URL'); + ?? $this->packages->getUrl(self::IMG_DEFAULT_AVATAR_PATH); } //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 } //Fallback to the default avatar picture - return $this->packages->getUrl('/img/default_avatar.png'); + return $this->packages->getUrl(self::IMG_DEFAULT_AVATAR_PATH); } public function getAvatarSmURL(User $user): string @@ -68,21 +79,16 @@ class UserAvatarHelper //Check if the user has a master attachment defined (meaning he has explicitly defined a profile picture) if ($user->getMasterPictureAttachment() instanceof Attachment) { return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_xs') - ?? throw new RuntimeException('Could not generate thumbnail URL');; + ?? $this->packages->getUrl(self::IMG_DEFAULT_AVATAR_PATH); } //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 } - try { - //Otherwise we can serve the relative path via Asset component - return $this->filterService->getUrlOfFilteredImage('/img/default_avatar.png', 'thumbnail_xs'); - } catch (RuntimeException) { - //If the filter fails, we can not serve the thumbnail and fall back to the original image and log an warning - return $this->packages->getUrl('/img/default_avatar.png'); - } + //Otherwise serve the default image (its an SVG, so we dont need to thumbnail it) + return $this->packages->getUrl(self::IMG_DEFAULT_AVATAR_PATH); } public function getAvatarMdURL(User $user): string @@ -90,28 +96,23 @@ class UserAvatarHelper //Check if the user has a master attachment defined (meaning he has explicitly defined a profile picture) if ($user->getMasterPictureAttachment() instanceof Attachment) { return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_sm') - ?? throw new RuntimeException('Could not generate thumbnail URL'); + ?? $this->packages->getUrl(self::IMG_DEFAULT_AVATAR_PATH); } //If not check if gravatar is enabled (then use gravatar URL) - if ($this->use_gravatar) { + if ($this->privacySettings->useGravatar) { return $this->getGravatar($user, 150); } - try { - //Otherwise we can serve the relative path via Asset component - return $this->filterService->getUrlOfFilteredImage('/img/default_avatar.png', 'thumbnail_xs'); - } catch (RuntimeException) { - //If the filter fails, we can not serve the thumbnail and fall back to the original image and log an warning - return $this->packages->getUrl('/img/default_avatar.png'); - } + //Otherwise serve the default image (its an SVG, so we dont need to thumbnail it) + return $this->packages->getUrl(self::IMG_DEFAULT_AVATAR_PATH); } /** * Get either a Gravatar URL or complete image tag for a specified email address. * - * @param User $user The user for which the gravator should be generated + * @param User $user The user for which the gravator should be generated * @param int $s Size in pixels, defaults to 80px [ 1 - 2048 ] * @param string $d Default imageset to use [ 404 | mm | identicon | monsterid | wavatar ] * @param string $r Maximum rating (inclusive) [ g | pg | r | x ] @@ -129,7 +130,7 @@ class UserAvatarHelper $url = 'https://www.gravatar.com/avatar/'; $url .= md5(strtolower(trim($email))); - return $url . "?s=${s}&d=${d}&r=${r}"; + return $url."?s=$s&d=$d&r=$r"; } /** @@ -157,11 +158,10 @@ class UserAvatarHelper } $attachment->setAttachmentType($attachment_type); - //$user->setMasterPictureAttachment($attachment); } //Handle the upload - $this->submitHandler->handleFormSubmit($attachment, $file); + $this->submitHandler->handleUpload($attachment, new AttachmentUpload(file: $file)); //Set attachment as master picture $user->setMasterPictureAttachment($attachment); diff --git a/src/Services/UserSystem/VoterHelper.php b/src/Services/UserSystem/VoterHelper.php new file mode 100644 index 00000000..d3c5368c --- /dev/null +++ b/src/Services/UserSystem/VoterHelper.php @@ -0,0 +1,152 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\UserSystem; + +use App\Entity\UserSystem\User; +use App\Repository\UserRepository; +use App\Security\ApiTokenAuthenticatedToken; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +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 + */ +final class VoterHelper +{ + private readonly UserRepository $userRepository; + private readonly array $permissionStructure; + + 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(); + } + + /** + * Checks if the operation on the given permission is granted for the given token. + * Similar to isGrantedTrinary, but returns false if the permission is not granted. + * @param TokenInterface $token The token to check + * @param string $permission The permission to check + * @param string $operation The operation to check + * @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, ?Vote $vote = null): bool + { + $tmp = $this->isGrantedTrinary($token, $permission, $operation) ?? false; + if ($tmp === false) { + $this->addReason($vote, $permission, $operation); + } + return $tmp; + } + + /** + * Checks if the operation on the given permission is granted for the given token. + * The result is returned in trinary value, where null means inherted from the parent. + * @param TokenInterface $token The token to check + * @param string $permission The permission to check + * @param string $operation The operation to check + * @return bool|null The result of the check. Null means inherted from the parent. + */ + public function isGrantedTrinary(TokenInterface $token, string $permission, string $operation): ?bool + { + $user = $token->getUser(); + + if ($user instanceof User) { + //A disallowed user is not allowed to do anything... + if ($user->isDisabled()) { + return false; + } + } else { + //Try to resolve the user from the token + $user = $this->resolveUser($token); + } + + //If the token is a APITokenAuthenticated + if ($token instanceof ApiTokenAuthenticatedToken) { + //Use the special API token checker + return $this->permissionManager->inheritWithAPILevel($user, $token->getRoleNames(), $permission, $operation); + } + + //Otherwise use the normal permission checker + return $this->permissionManager->inherit($user, $permission, $operation); + } + + /** + * Resolves the user from the given token. If the token is anonymous, the anonymous user is returned. + * @return User + */ + public function resolveUser(TokenInterface $token): User + { + $user = $token->getUser(); + //If the user is a User entity, just return it + if ($user instanceof User) { + return $user; + } + + //If the user is null, return the anonymous user + if ($user === null) { + $user = $this->userRepository->getAnonymousUser(); + if (!$user instanceof User) { + throw new \RuntimeException('The anonymous user could not be resolved.'); + } + return $user; + } + + //Otherwise throw an exception + throw new \RuntimeException('The user could not be resolved.'); + } + + /** + * Checks if the permission operation combination with the given names is existing. + * Just a proxy to the permission manager. + * + * @param string $permission the name of the permission which should be checked + * @param string $operation the name of the operation which should be checked + * + * @return bool true if the given permission operation combination is existing + */ + public function isValidOperation(string $permission, string $operation): bool + { + return $this->permissionManager->isValidOperation($permission, $operation); + } + + 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 00000000..14d9395e --- /dev/null +++ b/src/Settings/AppSettings.php @@ -0,0 +1,58 @@ +. + */ + +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 ?SynonymSettings $synonyms = 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 00000000..3053073f --- /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 00000000..f017c846 --- /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 00000000..c025c952 --- /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 00000000..cb0e60be --- /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 00000000..a1ff6985 --- /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 00000000..b3421e41 --- /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 00000000..f42c1c1c --- /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 00000000..a4cdbf0d --- /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 00000000..fac6aae9 --- /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 00000000..e6e258f5 --- /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 00000000..906838e2 --- /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 00000000..429fab56 --- /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 00000000..0abaa7f2 --- /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 00000000..77cf9080 --- /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 00000000..e479e07e --- /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 00000000..c28da459 --- /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 00000000..033d8b7e --- /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 00000000..588447de --- /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 00000000..d6f03d34 --- /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 00000000..744523c6 --- /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/IpnSuggestSettings.php b/src/Settings/MiscSettings/IpnSuggestSettings.php new file mode 100644 index 00000000..2c2cb21a --- /dev/null +++ b/src/Settings/MiscSettings/IpnSuggestSettings.php @@ -0,0 +1,109 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\MiscSettings; + +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\ParameterTypes\StringType; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Translation\StaticMessage; +use Symfony\Component\Translation\TranslatableMessage as TM; +use Symfony\Component\Validator\Constraints as Assert; + +#[Settings(label: new TM("settings.misc.ipn_suggest"))] +#[SettingsIcon("fa-arrow-up-1-9")] +class IpnSuggestSettings +{ + use SettingsTrait; + + #[SettingsParameter( + label: new TM("settings.misc.ipn_suggest.regex"), + description: new TM("settings.misc.ipn_suggest.regex.help"), + options: ['type' => StringType::class], + formOptions: ['attr' => ['placeholder' => new StaticMessage( '^[A-Za-z0-9]{3,4}(?:-[A-Za-z0-9]{3,4})*-\d{4}$')]], + envVar: "IPN_SUGGEST_REGEX", envVarMode: EnvVarMode::OVERWRITE, + )] + public ?string $regex = null; + + #[SettingsParameter( + label: new TM("settings.misc.ipn_suggest.regex_help"), + description: new TM("settings.misc.ipn_suggest.regex_help_description"), + options: ['type' => StringType::class], + formOptions: ['attr' => ['placeholder' => new TM('settings.misc.ipn_suggest.regex.help.placeholder')]], + envVar: "IPN_SUGGEST_REGEX_HELP", envVarMode: EnvVarMode::OVERWRITE, + )] + public ?string $regexHelp = null; + + #[SettingsParameter( + label: new TM("settings.misc.ipn_suggest.autoAppendSuffix"), + envVar: "bool:IPN_AUTO_APPEND_SUFFIX", envVarMode: EnvVarMode::OVERWRITE, + )] + public bool $autoAppendSuffix = false; + + #[SettingsParameter(label: new TM("settings.misc.ipn_suggest.suggestPartDigits"), + description: new TM("settings.misc.ipn_suggest.suggestPartDigits.help"), + formOptions: ['attr' => ['min' => 1, 'max' => 8]], + envVar: "int:IPN_SUGGEST_PART_DIGITS", envVarMode: EnvVarMode::OVERWRITE + )] + #[Assert\Range(min: 1, max: 8)] + public int $suggestPartDigits = 4; + + #[SettingsParameter( + label: new TM("settings.misc.ipn_suggest.useDuplicateDescription"), + description: new TM("settings.misc.ipn_suggest.useDuplicateDescription.help"), + envVar: "bool:IPN_USE_DUPLICATE_DESCRIPTION", envVarMode: EnvVarMode::OVERWRITE, + )] + public bool $useDuplicateDescription = false; + + #[SettingsParameter( + label: new TM("settings.misc.ipn_suggest.fallbackPrefix"), + description: new TM("settings.misc.ipn_suggest.fallbackPrefix.help"), + options: ['type' => StringType::class], + )] + public string $fallbackPrefix = 'N.A.'; + + #[SettingsParameter( + label: new TM("settings.misc.ipn_suggest.numberSeparator"), + description: new TM("settings.misc.ipn_suggest.numberSeparator.help"), + options: ['type' => StringType::class], + )] + public string $numberSeparator = '-'; + + #[SettingsParameter( + label: new TM("settings.misc.ipn_suggest.categorySeparator"), + description: new TM("settings.misc.ipn_suggest.categorySeparator.help"), + options: ['type' => StringType::class], + )] + public string $categorySeparator = '-'; + + #[SettingsParameter( + label: new TM("settings.misc.ipn_suggest.globalPrefix"), + description: new TM("settings.misc.ipn_suggest.globalPrefix.help"), + options: ['type' => StringType::class], + )] + public ?string $globalPrefix = null; +} diff --git a/src/Settings/MiscSettings/KiCadEDASettings.php b/src/Settings/MiscSettings/KiCadEDASettings.php new file mode 100644 index 00000000..d8f1026d --- /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 00000000..050dbcbc --- /dev/null +++ b/src/Settings/MiscSettings/MiscSettings.php @@ -0,0 +1,41 @@ +. + */ + +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; + + #[EmbeddedSettings] + public ?IpnSuggestSettings $ipnSuggestSettings = null; +} diff --git a/src/Settings/SettingsIcon.php b/src/Settings/SettingsIcon.php new file mode 100644 index 00000000..45bfc544 --- /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/SynonymSettings.php b/src/Settings/SynonymSettings.php new file mode 100644 index 00000000..25fc87e9 --- /dev/null +++ b/src/Settings/SynonymSettings.php @@ -0,0 +1,116 @@ +. + */ + +declare(strict_types=1); + +namespace App\Settings; + +use App\Form\Settings\TypeSynonymsCollectionType; +use App\Services\ElementTypes; +use Jbtronics\SettingsBundle\ParameterTypes\ArrayType; +use Jbtronics\SettingsBundle\ParameterTypes\SerializeType; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Translation\TranslatableMessage as TM; +use Symfony\Component\Validator\Constraints as Assert; + +#[Settings(label: new TM("settings.synonyms"), description: "settings.synonyms.help")] +#[SettingsIcon("fa-language")] +class SynonymSettings +{ + use SettingsTrait; + + #[SettingsParameter( + ArrayType::class, + label: new TM("settings.synonyms.type_synonyms"), + description: new TM("settings.synonyms.type_synonyms.help"), + options: ['type' => SerializeType::class], + formType: TypeSynonymsCollectionType::class, + formOptions: [ + 'required' => false, + ], + )] + #[Assert\Type('array')] + #[Assert\All([new Assert\Type('array')])] + /** + * @var array> $typeSynonyms + * An array of the form: [ + * 'category' => [ + * 'en' => ['singular' => 'Category', 'plural' => 'Categories'], + * 'de' => ['singular' => 'Kategorie', 'plural' => 'Kategorien'], + * ], + * 'manufacturer' => [ + * 'en' => ['singular' => 'Manufacturer', 'plural' =>'Manufacturers'], + * ], + * ] + */ + public array $typeSynonyms = []; + + /** + * Checks if there is any synonym defined for the given type (no matter which language). + * @param ElementTypes $type + * @return bool + */ + public function isSynonymDefinedForType(ElementTypes $type): bool + { + return isset($this->typeSynonyms[$type->value]) && count($this->typeSynonyms[$type->value]) > 0; + } + + /** + * Returns the singular synonym for the given type and locale, or null if none is defined. + * @param ElementTypes $type + * @param string $locale + * @return string|null + */ + public function getSingularSynonymForType(ElementTypes $type, string $locale): ?string + { + return $this->typeSynonyms[$type->value][$locale]['singular'] ?? null; + } + + /** + * Returns the plural synonym for the given type and locale, or null if none is defined. + * @param ElementTypes $type + * @param string|null $locale + * @return string|null + */ + public function getPluralSynonymForType(ElementTypes $type, ?string $locale): ?string + { + return $this->typeSynonyms[$type->value][$locale]['plural'] + ?? $this->typeSynonyms[$type->value][$locale]['singular'] + ?? null; + } + + /** + * Sets a synonym for the given type and locale. + * @param ElementTypes $type + * @param string $locale + * @param string $singular + * @param string $plural + * @return void + */ + public function setSynonymForType(ElementTypes $type, string $locale, string $singular, string $plural): void + { + $this->typeSynonyms[$type->value][$locale] = [ + 'singular' => $singular, + 'plural' => $plural, + ]; + } +} diff --git a/src/Settings/SystemSettings/AttachmentsSettings.php b/src/Settings/SystemSettings/AttachmentsSettings.php new file mode 100644 index 00000000..6d15c639 --- /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 00000000..623e6187 --- /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 00000000..46003c6d --- /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 00000000..7366dfa2 --- /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 00000000..c6780c6c --- /dev/null +++ b/src/Settings/SystemSettings/LocalizationSettings.php @@ -0,0 +1,76 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\SystemSettings; + +use App\Form\Settings\LanguageMenuEntriesType; +use App\Form\Type\LocaleSelectType; +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\ParameterTypes\ArrayType; +use Jbtronics\SettingsBundle\ParameterTypes\StringType; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +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 00000000..1ef3c635 --- /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 00000000..8cbeb560 --- /dev/null +++ b/src/Settings/SystemSettings/SystemSettings.php @@ -0,0 +1,49 @@ +. + */ + +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/CurrentApiTokenProvider.php b/src/State/CurrentApiTokenProvider.php new file mode 100644 index 00000000..f989d504 --- /dev/null +++ b/src/State/CurrentApiTokenProvider.php @@ -0,0 +1,49 @@ +. + */ + +declare(strict_types=1); + + +namespace App\State; + +use ApiPlatform\Metadata\Operation; +use ApiPlatform\State\ProviderInterface; +use App\Security\ApiTokenAuthenticatedToken; +use Symfony\Bundle\SecurityBundle\Security; + + +class CurrentApiTokenProvider implements ProviderInterface +{ + + public function __construct(private readonly Security $security) + { + + } + + public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null + { + $securityToken = $this->security->getToken(); + if (!$securityToken instanceof ApiTokenAuthenticatedToken) { + return null; + } + + return $securityToken->getApiToken(); + } +} \ No newline at end of file diff --git a/src/State/PartDBInfoProvider.php b/src/State/PartDBInfoProvider.php new file mode 100644 index 00000000..b3496cad --- /dev/null +++ b/src/State/PartDBInfoProvider.php @@ -0,0 +1,44 @@ +versionManager->getVersion()->toString(), + git_branch: $this->gitVersionInfo->getGitBranchName(), + git_commit: $this->gitVersionInfo->getGitCommitHash(), + title: $this->customizationSettings->instanceName, + banner: $this->bannerHelper->getBanner(), + default_uri: $this->default_uri, + global_timezone: $this->localizationSettings->timezone, + base_currency: $this->localizationSettings->baseCurrency, + global_locale: $this->localizationSettings->locale, + ); + } +} diff --git a/src/Translation/Fixes/SegmentAwareXliffFileDumper.php b/src/Translation/Fixes/SegmentAwareXliffFileDumper.php new file mode 100644 index 00000000..4b44ef01 --- /dev/null +++ b/src/Translation/Fixes/SegmentAwareXliffFileDumper.php @@ -0,0 +1,242 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Translation\Fixes; + +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; +use Symfony\Component\Translation\Dumper\FileDumper; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * Backport of the XliffFile dumper from Symfony 7.2, which supports segment attributes and notes, this keeps the + * metadata when editing the translations from inside Symfony. + */ +#[AsDecorator("translation.dumper.xliff")] +class SegmentAwareXliffFileDumper extends FileDumper +{ + + public function __construct( + private string $extension = 'xlf', + ) { + } + + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string + { + $xliffVersion = '1.2'; + if (\array_key_exists('xliff_version', $options)) { + $xliffVersion = $options['xliff_version']; + } + + if (\array_key_exists('default_locale', $options)) { + $defaultLocale = $options['default_locale']; + } else { + $defaultLocale = \Locale::getDefault(); + } + + if ('1.2' === $xliffVersion) { + return $this->dumpXliff1($defaultLocale, $messages, $domain, $options); + } + if ('2.0' === $xliffVersion) { + return $this->dumpXliff2($defaultLocale, $messages, $domain); + } + + throw new InvalidArgumentException(\sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion)); + } + + protected function getExtension(): string + { + return $this->extension; + } + + private function dumpXliff1(string $defaultLocale, MessageCatalogue $messages, ?string $domain, array $options = []): string + { + $toolInfo = ['tool-id' => 'symfony', 'tool-name' => 'Symfony']; + if (\array_key_exists('tool_info', $options)) { + $toolInfo = array_merge($toolInfo, $options['tool_info']); + } + + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + + $xliff = $dom->appendChild($dom->createElement('xliff')); + $xliff->setAttribute('version', '1.2'); + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:1.2'); + + $xliffFile = $xliff->appendChild($dom->createElement('file')); + $xliffFile->setAttribute('source-language', str_replace('_', '-', $defaultLocale)); + $xliffFile->setAttribute('target-language', str_replace('_', '-', $messages->getLocale())); + $xliffFile->setAttribute('datatype', 'plaintext'); + $xliffFile->setAttribute('original', 'file.ext'); + + $xliffHead = $xliffFile->appendChild($dom->createElement('header')); + $xliffTool = $xliffHead->appendChild($dom->createElement('tool')); + foreach ($toolInfo as $id => $value) { + $xliffTool->setAttribute($id, $value); + } + + if ($catalogueMetadata = $messages->getCatalogueMetadata('', $domain) ?? []) { + $xliffPropGroup = $xliffHead->appendChild($dom->createElement('prop-group')); + foreach ($catalogueMetadata as $key => $value) { + $xliffProp = $xliffPropGroup->appendChild($dom->createElement('prop')); + $xliffProp->setAttribute('prop-type', $key); + $xliffProp->appendChild($dom->createTextNode($value)); + } + } + + $xliffBody = $xliffFile->appendChild($dom->createElement('body')); + foreach ($messages->all($domain) as $source => $target) { + $translation = $dom->createElement('trans-unit'); + + $translation->setAttribute('id', strtr(substr(base64_encode(hash('xxh128', $source, true)), 0, 7), '/+', '._')); + $translation->setAttribute('resname', $source); + + $s = $translation->appendChild($dom->createElement('source')); + $s->appendChild($dom->createTextNode($source)); + + // Does the target contain characters requiring a CDATA section? + $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target); + + $targetElement = $dom->createElement('target'); + $metadata = $messages->getMetadata($source, $domain); + if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { + foreach ($metadata['target-attributes'] as $name => $value) { + $targetElement->setAttribute($name, $value); + } + } + $t = $translation->appendChild($targetElement); + $t->appendChild($text); + + if ($this->hasMetadataArrayInfo('notes', $metadata)) { + foreach ($metadata['notes'] as $note) { + if (!isset($note['content'])) { + continue; + } + + $n = $translation->appendChild($dom->createElement('note')); + $n->appendChild($dom->createTextNode($note['content'])); + + if (isset($note['priority'])) { + $n->setAttribute('priority', $note['priority']); + } + + if (isset($note['from'])) { + $n->setAttribute('from', $note['from']); + } + } + } + + $xliffBody->appendChild($translation); + } + + return $dom->saveXML(); + } + + private function dumpXliff2(string $defaultLocale, MessageCatalogue $messages, ?string $domain): string + { + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + + $xliff = $dom->appendChild($dom->createElement('xliff')); + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:2.0'); + $xliff->setAttribute('version', '2.0'); + $xliff->setAttribute('srcLang', str_replace('_', '-', $defaultLocale)); + $xliff->setAttribute('trgLang', str_replace('_', '-', $messages->getLocale())); + + $xliffFile = $xliff->appendChild($dom->createElement('file')); + if (str_ends_with($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX)) { + $xliffFile->setAttribute('id', substr($domain, 0, -\strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX)).'.'.$messages->getLocale()); + } else { + $xliffFile->setAttribute('id', $domain.'.'.$messages->getLocale()); + } + + if ($catalogueMetadata = $messages->getCatalogueMetadata('', $domain) ?? []) { + $xliff->setAttribute('xmlns:m', 'urn:oasis:names:tc:xliff:metadata:2.0'); + $xliffMetadata = $xliffFile->appendChild($dom->createElement('m:metadata')); + foreach ($catalogueMetadata as $key => $value) { + $xliffMeta = $xliffMetadata->appendChild($dom->createElement('prop')); + $xliffMeta->setAttribute('type', $key); + $xliffMeta->appendChild($dom->createTextNode($value)); + } + } + + foreach ($messages->all($domain) as $source => $target) { + $translation = $dom->createElement('unit'); + $translation->setAttribute('id', strtr(substr(base64_encode(hash('xxh128', $source, true)), 0, 7), '/+', '._')); + + if (\strlen($source) <= 80) { + $translation->setAttribute('name', $source); + } + + $metadata = $messages->getMetadata($source, $domain); + + // Add notes section + if ($this->hasMetadataArrayInfo('notes', $metadata)) { + $notesElement = $dom->createElement('notes'); + foreach ($metadata['notes'] as $note) { + $n = $dom->createElement('note'); + $n->appendChild($dom->createTextNode($note['content'] ?? '')); + unset($note['content']); + + foreach ($note as $name => $value) { + $n->setAttribute($name, $value); + } + $notesElement->appendChild($n); + } + $translation->appendChild($notesElement); + } + + $segment = $translation->appendChild($dom->createElement('segment')); + + if ($this->hasMetadataArrayInfo('segment-attributes', $metadata)) { + foreach ($metadata['segment-attributes'] as $name => $value) { + $segment->setAttribute($name, $value); + } + } + + $s = $segment->appendChild($dom->createElement('source')); + $s->appendChild($dom->createTextNode($source)); + + // Does the target contain characters requiring a CDATA section? + $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target); + + $targetElement = $dom->createElement('target'); + if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { + foreach ($metadata['target-attributes'] as $name => $value) { + $targetElement->setAttribute($name, $value); + } + } + $t = $segment->appendChild($targetElement); + $t->appendChild($text); + + $xliffFile->appendChild($translation); + } + + return $dom->saveXML(); + } + + private function hasMetadataArrayInfo(string $key, ?array $metadata = null): bool + { + return is_iterable($metadata[$key] ?? null); + } +} \ No newline at end of file diff --git a/src/Translation/Fixes/SegmentAwareXliffFileLoader.php b/src/Translation/Fixes/SegmentAwareXliffFileLoader.php new file mode 100644 index 00000000..12455e87 --- /dev/null +++ b/src/Translation/Fixes/SegmentAwareXliffFileLoader.php @@ -0,0 +1,262 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Translation\Fixes; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Util\Exception\InvalidXmlException; +use Symfony\Component\Config\Util\Exception\XmlParsingException; +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Translation\Exception\RuntimeException; +use Symfony\Component\Translation\Loader\LoaderInterface; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Util\XliffUtils; + +/** + * Backport of the XliffFile dumper from Symfony 7.2, which supports segment attributes and notes, this keeps the + * metadata when editing the translations from inside Symfony. + */ +#[AsDecorator("translation.loader.xliff")] +class SegmentAwareXliffFileLoader implements LoaderInterface +{ + public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue + { + if (!class_exists(XmlUtils::class)) { + throw new RuntimeException('Loading translations from the Xliff format requires the Symfony Config component.'); + } + + if (!$this->isXmlString($resource)) { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(\sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(\sprintf('File "%s" not found.', $resource)); + } + + if (!is_file($resource)) { + throw new InvalidResourceException(\sprintf('This is neither a file nor an XLIFF string "%s".', $resource)); + } + } + + try { + if ($this->isXmlString($resource)) { + $dom = XmlUtils::parse($resource); + } else { + $dom = XmlUtils::loadFile($resource); + } + } catch (\InvalidArgumentException|XmlParsingException|InvalidXmlException $e) { + throw new InvalidResourceException(\sprintf('Unable to load "%s": ', $resource).$e->getMessage(), $e->getCode(), $e); + } + + if ($errors = XliffUtils::validateSchema($dom)) { + throw new InvalidResourceException(\sprintf('Invalid resource provided: "%s"; Errors: ', $resource).XliffUtils::getErrorsAsString($errors)); + } + + $catalogue = new MessageCatalogue($locale); + $this->extract($dom, $catalogue, $domain); + + if (is_file($resource) && class_exists(FileResource::class)) { + $catalogue->addResource(new FileResource($resource)); + } + + return $catalogue; + } + + private function extract(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void + { + $xliffVersion = XliffUtils::getVersionNumber($dom); + + if ('1.2' === $xliffVersion) { + $this->extractXliff1($dom, $catalogue, $domain); + } + + if ('2.0' === $xliffVersion) { + $this->extractXliff2($dom, $catalogue, $domain); + } + } + + /** + * Extract messages and metadata from DOMDocument into a MessageCatalogue. + */ + private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void + { + $xml = simplexml_import_dom($dom); + $encoding = $dom->encoding ? strtoupper($dom->encoding) : null; + + $namespace = 'urn:oasis:names:tc:xliff:document:1.2'; + $xml->registerXPathNamespace('xliff', $namespace); + + foreach ($xml->xpath('//xliff:file') as $file) { + $fileAttributes = $file->attributes(); + + $file->registerXPathNamespace('xliff', $namespace); + + foreach ($file->xpath('.//xliff:prop') as $prop) { + $catalogue->setCatalogueMetadata($prop->attributes()['prop-type'], (string) $prop, $domain); + } + + foreach ($file->xpath('.//xliff:trans-unit') as $translation) { + $attributes = $translation->attributes(); + + if (!(isset($attributes['resname']) || isset($translation->source))) { + continue; + } + + $source = (string) (isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source); + + if (isset($translation->target) + && 'needs-translation' === (string) $translation->target->attributes()['state'] + && \in_array((string) $translation->target, [$source, (string) $translation->source], true) + ) { + continue; + } + + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) ($translation->target ?? $translation->source), $encoding); + + $catalogue->set($source, $target, $domain); + + $metadata = [ + 'source' => (string) $translation->source, + 'file' => [ + 'original' => (string) $fileAttributes['original'], + ], + ]; + if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) { + $metadata['notes'] = $notes; + } + + if (isset($translation->target) && $translation->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($translation->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } + } + + if (isset($attributes['id'])) { + $metadata['id'] = (string) $attributes['id']; + } + + $catalogue->setMetadata($source, $metadata, $domain); + } + } + } + + private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void + { + $xml = simplexml_import_dom($dom); + $encoding = $dom->encoding ? strtoupper($dom->encoding) : null; + + $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0'); + + foreach ($xml->xpath('//xliff:unit') as $unit) { + foreach ($unit->segment as $segment) { + $attributes = $unit->attributes(); + $source = $attributes['name'] ?? $segment->source; + + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) ($segment->target ?? $segment->source), $encoding); + + $catalogue->set((string) $source, $target, $domain); + + $metadata = []; + if ($segment->attributes()) { + $metadata['segment-attributes'] = []; + foreach ($segment->attributes() as $key => $value) { + $metadata['segment-attributes'][$key] = (string) $value; + } + } + + if (isset($segment->target) && $segment->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($segment->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } + } + + if (isset($unit->notes)) { + $metadata['notes'] = []; + foreach ($unit->notes->note as $noteNode) { + $note = []; + foreach ($noteNode->attributes() as $key => $value) { + $note[$key] = (string) $value; + } + $note['content'] = (string) $noteNode; + $metadata['notes'][] = $note; + } + } + + $catalogue->setMetadata((string) $source, $metadata, $domain); + } + } + } + + /** + * Convert a UTF8 string to the specified encoding. + */ + private function utf8ToCharset(string $content, ?string $encoding = null): string + { + if ('UTF-8' !== $encoding && $encoding) { + return mb_convert_encoding($content, $encoding, 'UTF-8'); + } + + return $content; + } + + private function parseNotesMetadata(?\SimpleXMLElement $noteElement = null, ?string $encoding = null): array + { + $notes = []; + + if (null === $noteElement) { + return $notes; + } + + /** @var \SimpleXMLElement $xmlNote */ + foreach ($noteElement as $xmlNote) { + $noteAttributes = $xmlNote->attributes(); + $note = ['content' => $this->utf8ToCharset((string) $xmlNote, $encoding)]; + if (isset($noteAttributes['priority'])) { + $note['priority'] = (int) $noteAttributes['priority']; + } + + if (isset($noteAttributes['from'])) { + $note['from'] = (string) $noteAttributes['from']; + } + + $notes[] = $note; + } + + return $notes; + } + + private function isXmlString(string $resource): bool + { + return str_starts_with($resource, ' $this->nameGenerator->getLocalizedTypeLabel($entity)), + new TwigFunction('type_label', fn(object|string $entity): string => $this->nameGenerator->typeLabel($entity)), + new TwigFunction('type_label_p', fn(object|string $entity): string => $this->nameGenerator->typeLabelPlural($entity)), ]; } @@ -104,7 +107,7 @@ final class EntityExtension extends AbstractExtension $map = [ Part::class => 'part', Footprint::class => 'footprint', - Storelocation::class => 'storelocation', + StorageLocation::class => 'storelocation', Manufacturer::class => 'manufacturer', Category::class => 'category', Project::class => 'device', @@ -115,6 +118,7 @@ final class EntityExtension extends AbstractExtension Currency::class => 'currency', MeasurementUnit::class => 'measurement_unit', LabelProfile::class => 'label_profile', + PartCustomState::class => 'part_custom_state', ]; foreach ($map as $class => $type) { diff --git a/src/Twig/FormatExtension.php b/src/Twig/FormatExtension.php index 76628ccd..46313aaf 100644 --- a/src/Twig/FormatExtension.php +++ b/src/Twig/FormatExtension.php @@ -82,7 +82,7 @@ final class FormatExtension extends AbstractExtension public function formatBytes(int $bytes, int $precision = 2): string { $size = ['B','kB','MB','GB','TB','PB','EB','ZB','YB']; - $factor = 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/InfoProviderExtension.php b/src/Twig/InfoProviderExtension.php index 7cb04db4..a963b778 100644 --- a/src/Twig/InfoProviderExtension.php +++ b/src/Twig/InfoProviderExtension.php @@ -51,7 +51,7 @@ class InfoProviderExtension extends AbstractExtension { try { return $this->providerRegistry->getProviderByKey($key); - } catch (\InvalidArgumentException $exception) { + } catch (\InvalidArgumentException) { return null; } } @@ -65,7 +65,7 @@ class InfoProviderExtension extends AbstractExtension { try { return $this->providerRegistry->getProviderByKey($key)->getProviderInfo()['name']; - } catch (\InvalidArgumentException $exception) { + } catch (\InvalidArgumentException) { return null; } } diff --git a/src/Twig/MiscExtension.php b/src/Twig/MiscExtension.php index 1edef7a3..8b6ebc68 100644 --- a/src/Twig/MiscExtension.php +++ b/src/Twig/MiscExtension.php @@ -22,6 +22,11 @@ declare(strict_types=1); */ 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; @@ -35,9 +40,54 @@ final class MiscExtension extends AbstractExtension 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 + * @return string + */ + public function uri_without_host(Request $request): string + { + if (null !== $qs = $request->getQueryString()) { + $qs = '?'.$qs; + } + + return $request->getBaseUrl().$request->getPathInfo().$qs; + } } diff --git a/src/Twig/Sandbox/InheritanceSecurityPolicy.php b/src/Twig/Sandbox/InheritanceSecurityPolicy.php index db1cd2b5..06ab3a1f 100644 --- a/src/Twig/Sandbox/InheritanceSecurityPolicy.php +++ b/src/Twig/Sandbox/InheritanceSecurityPolicy.php @@ -22,7 +22,6 @@ use Twig\Sandbox\SecurityNotAllowedTagError; use Twig\Sandbox\SecurityPolicyInterface; use Twig\Template; -use function get_class; use function in_array; use function is_array; @@ -35,9 +34,14 @@ use function is_array; */ final class InheritanceSecurityPolicy implements SecurityPolicyInterface { + /** + * @var array + */ private array $allowedMethods; - public function __construct(private array $allowedTags = [], private array $allowedFilters = [], array $allowedMethods = [], 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/Sandbox/SandboxedLabelExtension.php b/src/Twig/Sandbox/SandboxedLabelExtension.php new file mode 100644 index 00000000..59fb0af0 --- /dev/null +++ b/src/Twig/Sandbox/SandboxedLabelExtension.php @@ -0,0 +1,51 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Twig\Sandbox; + +use App\Services\LabelSystem\LabelTextReplacer; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; +use Twig\TwigFunction; + +class SandboxedLabelExtension extends AbstractExtension +{ + public function __construct(private readonly LabelTextReplacer $labelTextReplacer) + { + + } + + public function getFunctions(): array + { + return [ + new TwigFunction('placeholder', fn(string $text, object $label_target) => $this->labelTextReplacer->handlePlaceholderOrReturnNull($text, $label_target)), + ]; + } + + public function getFilters(): array + { + return [ + new TwigFilter('placeholders', fn(string $text, object $label_target) => $this->labelTextReplacer->replace($text, $label_target)), + ]; + } +} \ No newline at end of file diff --git a/src/Twig/TwigCoreExtension.php b/src/Twig/TwigCoreExtension.php index b77ff28b..7b2b58f8 100644 --- a/src/Twig/TwigCoreExtension.php +++ b/src/Twig/TwigCoreExtension.php @@ -34,15 +34,18 @@ use Twig\TwigTest; */ 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 { return [ /* Returns the enum cases as values */ - new TwigFunction('enum_cases', [$this, 'getEnumCases']), + new TwigFunction('enum_cases', $this->getEnumCases(...)), ]; } @@ -69,6 +72,7 @@ final class TwigCoreExtension extends AbstractExtension throw new \InvalidArgumentException(sprintf('The given class "%s" is not an enum!', $enum_class)); } + /** @noinspection PhpUndefinedMethodInspection */ return ($enum_class)::cases(); } diff --git a/src/Twig/UserExtension.php b/src/Twig/UserExtension.php index 93ea57be..5045257a 100644 --- a/src/Twig/UserExtension.php +++ b/src/Twig/UserExtension.php @@ -127,7 +127,7 @@ final class UserExtension extends AbstractExtension public function removeLocaleFromPath(string $path): string { //Ensure the path has the correct format - if (!preg_match('/^\/\w{2}\//', $path)) { + if (!preg_match('/^\/\w{2}(?:_\w{2})?\//', $path)) { throw new \InvalidArgumentException('The given path is not a localized path!'); } diff --git a/src/Validator/Constraints/NoLockoutValidator.php b/src/Validator/Constraints/NoLockoutValidator.php index 9d51d81e..f3998188 100644 --- a/src/Validator/Constraints/NoLockoutValidator.php +++ b/src/Validator/Constraints/NoLockoutValidator.php @@ -44,7 +44,7 @@ class NoLockoutValidator extends ConstraintValidator * @param mixed $value The value that should be validated * @param Constraint $constraint The constraint for the validation */ - public function validate($value, Constraint $constraint): void + public function validate(mixed $value, Constraint $constraint): void { if (!$constraint instanceof NoLockout) { throw new UnexpectedTypeException($constraint, NoLockout::class); diff --git a/src/Validator/Constraints/NoneOfItsChildrenValidator.php b/src/Validator/Constraints/NoneOfItsChildrenValidator.php index 3846c2cc..2be5f16b 100644 --- a/src/Validator/Constraints/NoneOfItsChildrenValidator.php +++ b/src/Validator/Constraints/NoneOfItsChildrenValidator.php @@ -30,6 +30,7 @@ use Symfony\Component\Validator\Exception\UnexpectedValueException; /** * The validator for the NoneOfItsChildren annotation. + * @see \App\Tests\Validator\Constraints\NoneOfItsChildrenValidatorTest */ class NoneOfItsChildrenValidator extends ConstraintValidator { @@ -39,7 +40,7 @@ class NoneOfItsChildrenValidator extends ConstraintValidator * @param mixed $value The value that should be validated * @param Constraint $constraint The constraint for the validation */ - public function validate($value, Constraint $constraint): void + public function validate(mixed $value, Constraint $constraint): void { if (!$constraint instanceof NoneOfItsChildren) { throw new UnexpectedTypeException($constraint, NoneOfItsChildren::class); diff --git a/src/Validator/Constraints/Selectable.php b/src/Validator/Constraints/Selectable.php index f65cb685..c26e47fa 100644 --- a/src/Validator/Constraints/Selectable.php +++ b/src/Validator/Constraints/Selectable.php @@ -31,5 +31,5 @@ use Symfony\Component\Validator\Constraint; #[\Attribute(\Attribute::TARGET_PROPERTY)] class Selectable extends Constraint { - public $message = 'validator.isSelectable'; + public string $message = 'validator.isSelectable'; } diff --git a/src/Validator/Constraints/SelectableValidator.php b/src/Validator/Constraints/SelectableValidator.php index 8519d230..013a3964 100644 --- a/src/Validator/Constraints/SelectableValidator.php +++ b/src/Validator/Constraints/SelectableValidator.php @@ -30,6 +30,7 @@ use Symfony\Component\Validator\Exception\UnexpectedValueException; /** * The validator for the Selectable constraint. + * @see \App\Tests\Validator\Constraints\SelectableValidatorTest */ class SelectableValidator extends ConstraintValidator { @@ -39,7 +40,7 @@ class SelectableValidator extends ConstraintValidator * @param mixed $value The value that should be validated * @param Constraint $constraint The constraint for the validation */ - public function validate($value, Constraint $constraint): void + public function validate(mixed $value, Constraint $constraint): void { if (!$constraint instanceof Selectable) { throw new UnexpectedTypeException($constraint, Selectable::class); diff --git a/src/Validator/Constraints/UniqueObjectCollection.php b/src/Validator/Constraints/UniqueObjectCollection.php index 574959a8..6548494e 100644 --- a/src/Validator/Constraints/UniqueObjectCollection.php +++ b/src/Validator/Constraints/UniqueObjectCollection.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator\Constraints; use InvalidArgumentException; @@ -34,19 +36,19 @@ class UniqueObjectCollection extends Constraint self::IS_NOT_UNIQUE => 'IS_NOT_UNIQUE', ]; - public string $message = 'This collection should contain only unique elements.'; + public string $message = 'This value is already used.'; public $normalizer; /** * @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); @@ -59,4 +61,4 @@ class UniqueObjectCollection extends Constraint throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); } } -} \ No newline at end of file +} diff --git a/src/Validator/Constraints/UniqueObjectCollectionValidator.php b/src/Validator/Constraints/UniqueObjectCollectionValidator.php index 5522ca19..b80889a4 100644 --- a/src/Validator/Constraints/UniqueObjectCollectionValidator.php +++ b/src/Validator/Constraints/UniqueObjectCollectionValidator.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator\Constraints; -use App\Entity\Base\AbstractDBElement; use App\Validator\UniqueValidatableInterface; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -use Symfony\Component\Serializer\Serializer; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\UnexpectedValueException; +/** + * @see \App\Tests\Validator\Constraints\UniqueObjectCollectionValidatorTest + */ class UniqueObjectCollectionValidator extends ConstraintValidator { - public function validate(mixed $value, Constraint $constraint) + public function validate(mixed $value, Constraint $constraint): void { if (!$constraint instanceof UniqueObjectCollection) { throw new UnexpectedTypeException($constraint, UniqueObjectCollection::class); @@ -86,11 +88,7 @@ class UniqueObjectCollectionValidator extends ConstraintValidator private function getNormalizer(UniqueObjectCollection $unique): callable { - if (null === $unique->normalizer) { - return static fn ($value) => $value; - } - - return $unique->normalizer; + return $unique->normalizer ?? static fn($value) => $value; } private function reduceElementKeys(array $fields, array $element, UniqueObjectCollection $constraint): array @@ -113,4 +111,4 @@ class UniqueObjectCollectionValidator extends ConstraintValidator return $output; } -} \ No newline at end of file +} diff --git a/src/Validator/Constraints/UniquePartIpnConstraint.php b/src/Validator/Constraints/UniquePartIpnConstraint.php new file mode 100644 index 00000000..ca32f9ef --- /dev/null +++ b/src/Validator/Constraints/UniquePartIpnConstraint.php @@ -0,0 +1,22 @@ +entityManager = $entityManager; + $this->ipnSuggestSettings = $ipnSuggestSettings; + } + + public function validate($value, Constraint $constraint): void + { + if (null === $value || '' === $value) { + return; + } + + //If the autoAppendSuffix option is enabled, the IPN becomes unique automatically later + if ($this->ipnSuggestSettings->autoAppendSuffix) { + return; + } + + if (!$constraint instanceof UniquePartIpnConstraint) { + return; + } + + /** @var Part $currentPart */ + $currentPart = $this->context->getObject(); + + if (!$currentPart instanceof Part) { + return; + } + + $repository = $this->entityManager->getRepository(Part::class); + $existingParts = $repository->findBy(['ipn' => $value]); + + foreach ($existingParts as $existingPart) { + if ($currentPart->getId() !== $existingPart->getId()) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $value) + ->addViolation(); + } + } + } +} diff --git a/src/Validator/Constraints/UrlOrBuiltinValidator.php b/src/Validator/Constraints/UrlOrBuiltinValidator.php index af498d2a..71407a6a 100644 --- a/src/Validator/Constraints/UrlOrBuiltinValidator.php +++ b/src/Validator/Constraints/UrlOrBuiltinValidator.php @@ -34,6 +34,7 @@ use function is_object; * The validator for UrlOrBuiltin. * It checks if the value is either a builtin ressource or a valid url. * In both cases it is not checked, if the ressource is really existing. + * @see \App\Tests\Validator\Constraints\UrlOrBuiltinValidatorTest */ class UrlOrBuiltinValidator extends UrlValidator { diff --git a/src/Validator/Constraints/ValidFileFilterValidator.php b/src/Validator/Constraints/ValidFileFilterValidator.php index d591a968..2a90a010 100644 --- a/src/Validator/Constraints/ValidFileFilterValidator.php +++ b/src/Validator/Constraints/ValidFileFilterValidator.php @@ -42,7 +42,7 @@ class ValidFileFilterValidator extends ConstraintValidator * @param mixed $value The value that should be validated * @param Constraint $constraint The constraint for the validation */ - public function validate($value, Constraint $constraint): void + public function validate(mixed $value, Constraint $constraint): void { if (!$constraint instanceof ValidFileFilter) { throw new UnexpectedTypeException($constraint, ValidFileFilter::class); diff --git a/src/Validator/Constraints/ValidGoogleAuthCode.php b/src/Validator/Constraints/ValidGoogleAuthCode.php index 482af35c..180d346e 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/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php b/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php index be935a71..25afe57b 100644 --- a/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php +++ b/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php @@ -22,12 +22,9 @@ declare(strict_types=1); namespace App\Validator\Constraints; -use App\Entity\UserSystem\User; use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface; -use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticatorInterface; use Symfony\Bundle\SecurityBundle\Security; -use Symfony\Component\Form\FormInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; @@ -36,9 +33,12 @@ use Symfony\Component\Validator\Exception\UnexpectedValueException; use function is_string; use function strlen; +/** + * @see \App\Tests\Validator\Constraints\ValidGoogleAuthCodeValidatorTest + */ class ValidGoogleAuthCodeValidator extends ConstraintValidator { - public function __construct(private GoogleAuthenticatorInterface $googleAuthenticator, private Security $security) + public function __construct(private readonly GoogleAuthenticatorInterface $googleAuthenticator, private readonly Security $security) { } diff --git a/src/Validator/Constraints/ValidPartLotValidator.php b/src/Validator/Constraints/ValidPartLotValidator.php index 4f988362..316fedea 100644 --- a/src/Validator/Constraints/ValidPartLotValidator.php +++ b/src/Validator/Constraints/ValidPartLotValidator.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace App\Validator\Constraints; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\Exception\UnexpectedTypeException; @@ -42,7 +42,7 @@ class ValidPartLotValidator extends ConstraintValidator * @param mixed $value The value that should be validated * @param Constraint $constraint The constraint for the validation */ - public function validate($value, Constraint $constraint): void + public function validate(mixed $value, Constraint $constraint): void { if (!$constraint instanceof ValidPartLot) { throw new UnexpectedTypeException($constraint, ValidPartLot::class); @@ -53,8 +53,8 @@ class ValidPartLotValidator extends ConstraintValidator } //We can only validate the values if we know the storelocation - if ($value->getStorageLocation() instanceof Storelocation) { - $repo = $this->em->getRepository(Storelocation::class); + if ($value->getStorageLocation() instanceof StorageLocation) { + $repo = $this->em->getRepository(StorageLocation::class); //We can only determine associated parts, if the part have an ID //When the storage location is new (no ID), we can just assume there are no other parts if (null !== $value->getID() && $value->getStorageLocation()->getID()) { diff --git a/src/Validator/Constraints/ValidPermissionValidator.php b/src/Validator/Constraints/ValidPermissionValidator.php index c0004e6c..afb7721b 100644 --- a/src/Validator/Constraints/ValidPermissionValidator.php +++ b/src/Validator/Constraints/ValidPermissionValidator.php @@ -22,15 +22,21 @@ declare(strict_types=1); namespace App\Validator\Constraints; +use App\Controller\GroupController; +use App\Controller\UserController; use App\Security\Interfaces\HasPermissionsInterface; use App\Services\UserSystem\PermissionManager; use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; +use function Symfony\Component\Translation\t; + class ValidPermissionValidator extends ConstraintValidator { - public function __construct(protected PermissionManager $resolver) + public function __construct(protected PermissionManager $resolver, protected RequestStack $requestStack) { } @@ -40,7 +46,7 @@ class ValidPermissionValidator extends ConstraintValidator * @param mixed $value The value that should be validated * @param Constraint $constraint The constraint for the validation */ - public function validate($value, Constraint $constraint): void + public function validate(mixed $value, Constraint $constraint): void { if (!$constraint instanceof ValidPermission) { throw new UnexpectedTypeException($constraint, ValidPermission::class); @@ -49,6 +55,26 @@ class ValidPermissionValidator extends ConstraintValidator /** @var HasPermissionsInterface $perm_holder */ $perm_holder = $this->context->getObject(); - $this->resolver->ensureCorrectSetOperations($perm_holder); + $changed = $this->resolver->ensureCorrectSetOperations($perm_holder); + + //Sending a flash message if the permissions were fixed (only if called from UserController or GroupController) + //This is pretty hacky and bad design but I dont see a better way without a complete rewrite of how permissions are validated + //on the admin pages + if ($changed) { + //Check if this was called in context of UserController + $request = $this->requestStack->getMainRequest(); + if ($request === null) { + return; + } + //Determine the controller class (the part before the ::) + $controller_class = explode('::', (string) $request->attributes->get('_controller'))[0]; + + if (in_array($controller_class, [UserController::class, GroupController::class], true)) { + /** @var Session $session */ + $session = $this->requestStack->getSession(); + $flashBag = $session->getFlashBag(); + $flashBag->add('warning', t('user.edit.flash.permissions_fixed')); + } + } } } diff --git a/src/Validator/Constraints/ValidThemeValidator.php b/src/Validator/Constraints/ValidThemeValidator.php index 5d222934..713be9a5 100644 --- a/src/Validator/Constraints/ValidThemeValidator.php +++ b/src/Validator/Constraints/ValidThemeValidator.php @@ -26,6 +26,9 @@ use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; +/** + * @see \App\Tests\Validator\Constraints\ValidThemeValidatorTest + */ class ValidThemeValidator extends ConstraintValidator { public function __construct(private readonly array $available_themes) diff --git a/src/Validator/Constraints/Year2038BugWorkaround.php b/src/Validator/Constraints/Year2038BugWorkaround.php new file mode 100644 index 00000000..04a07908 --- /dev/null +++ b/src/Validator/Constraints/Year2038BugWorkaround.php @@ -0,0 +1,41 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * Datetime interfaces properties with this constraint are limited to the year 2038 on 32-bit systems, to prevent a + * Year 2038 bug during rendering. + * + * Current PHP versions can not format dates after 2038 on 32-bit systems and throw an exception. + * (See https://github.com/Part-DB/Part-DB-server/discussions/548). + * + * This constraint does not fix that problem, but can prevent users from entering such invalid dates. + */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] +class Year2038BugWorkaround extends Constraint +{ + public string $message = 'validator.year_2038_bug_on_32bit'; +} \ No newline at end of file diff --git a/src/Validator/Constraints/Year2038BugWorkaroundValidator.php b/src/Validator/Constraints/Year2038BugWorkaroundValidator.php new file mode 100644 index 00000000..747721f9 --- /dev/null +++ b/src/Validator/Constraints/Year2038BugWorkaroundValidator.php @@ -0,0 +1,74 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Validator\Constraints; + +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +class Year2038BugWorkaroundValidator extends ConstraintValidator +{ + + public function __construct( + #[Autowire(env: "DISABLE_YEAR2038_BUG_CHECK")] + private readonly bool $disable_validation = false + ) + { + } + + public function isActivated(): bool + { + //If we are on a 32 bit system and the validation is not disabled, we should activate the validation + return !$this->disable_validation && PHP_INT_SIZE === 4; + } + + public function validate(mixed $value, Constraint $constraint): void + { + if (!$this->isActivated()) { + return; + } + + //If the value is null, we don't need to validate it + if ($value === null) { + return; + } + + //Ensure that we check the correct constraint + if (!$constraint instanceof Year2038BugWorkaround) { + throw new \InvalidArgumentException('This validator can only validate Year2038Bug constraints'); + } + + //We can only validate DateTime objects + if (!$value instanceof \DateTimeInterface) { + throw new UnexpectedTypeException($value, \DateTimeInterface::class); + } + + //If we reach here the validation is active and we should forbid any date after 2038. + if ($value->diff(new \DateTime('2038-01-19 03:14:06'))->invert === 1) { + $this->context->buildViolation($constraint->message) + ->addViolation(); + } + } +} \ No newline at end of file diff --git a/src/Validator/UniqueValidatableInterface.php b/src/Validator/UniqueValidatableInterface.php index 97e3a0b9..3d954490 100644 --- a/src/Validator/UniqueValidatableInterface.php +++ b/src/Validator/UniqueValidatableInterface.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator; interface UniqueValidatableInterface @@ -29,4 +31,4 @@ interface UniqueValidatableInterface * @return array An array of the form ['field1' => 'value1', 'field2' => 'value2', ...] */ public function getComparableFields(): array; -} \ No newline at end of file +} diff --git a/symfony.lock b/symfony.lock index d47e131c..3fce7c93 100644 --- a/symfony.lock +++ b/symfony.lock @@ -1,9 +1,17 @@ { - "amphp/amp": { - "version": "v2.2.1" - }, - "amphp/byte-stream": { - "version": "v1.6.1" + "api-platform/symfony": { + "version": "4.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "4.0", + "ref": "e9952e9f393c2d048f10a78f272cd35e807d972b" + }, + "files": [ + "./config/packages/api_platform.yaml", + "./config/routes/api_platform.yaml", + "./src/ApiResource/.gitignore" + ] }, "beberlei/assert": { "version": "v3.2.6" @@ -20,49 +28,24 @@ "composer/package-versions-deprecated": { "version": "1.11.99.4" }, - "composer/pcre": { - "version": "1.0.0" - }, - "composer/semver": { - "version": "1.5.0" - }, - "composer/xdebug-handler": { - "version": "1.3.3" - }, "dama/doctrine-test-bundle": { - "version": "4.0", + "version": "8.3", "recipe": { "repo": "github.com/symfony/recipes-contrib", - "branch": "master", - "version": "4.0", - "ref": "56eaa387b5e48ebcc7c95a893b47dfa1ad51449c" + "branch": "main", + "version": "8.3", + "ref": "dfc51177476fb39d014ed89944cde53dc3326d23" }, "files": [ - "./config/packages/test/dama_doctrine_test_bundle.yaml" + "config/packages/dama_doctrine_test_bundle.yaml" ] }, - "dnoegel/php-xdg-base-dir": { - "version": "v0.1.1" - }, - "doctrine/annotations": { - "version": "1.14", - "recipe": { - "repo": "github.com/symfony/recipes", - "branch": "main", - "version": "1.10", - "ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05" - }, - "files": [] - }, "doctrine/cache": { "version": "v1.8.0" }, "doctrine/collections": { "version": "v1.5.0" }, - "doctrine/common": { - "version": "v2.10.0" - }, "doctrine/data-fixtures": { "version": "v1.3.2" }, @@ -70,15 +53,21 @@ "version": "v2.9.2" }, "doctrine/deprecations": { - "version": "v0.5.3" - }, - "doctrine/doctrine-bundle": { - "version": "2.10", + "version": "1.1", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "2.10", - "ref": "f0d8c9a4da17815830aac0d63e153a940ae176bb" + "version": "1.0", + "ref": "87424683adc81d7dc305eefec1fced883084aab9" + } + }, + "doctrine/doctrine-bundle": { + "version": "2.15", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.13", + "ref": "620b57f496f2e599a6015a9fa222c2ee0a32adcb" }, "files": [ "config/packages/doctrine.yaml", @@ -144,41 +133,26 @@ "ekino/phpstan-banned-code": { "version": "v0.3.1" }, - "erusev/parsedown": { - "version": "1.7.4" - }, - "felixfbecker/advanced-json-rpc": { - "version": "v3.0.4" - }, - "felixfbecker/language-server-protocol": { - "version": "v1.4.0" - }, - "florianv/exchanger": { - "version": "1.4.1" - }, - "florianv/swap": { - "version": "3.5.0" - }, - "florianv/swap-bundle": { - "version": "5.0.0" - }, - "friendsofphp/proxy-manager-lts": { - "version": "v1.0.5" - }, "gregwar/captcha": { "version": "v1.1.7" }, "gregwar/captcha-bundle": { - "version": "v2.0.6" + "version": "v2.2.0" }, "imagine/imagine": { "version": "1.2.2" }, "jbtronics/2fa-webauthn": { - "version": "dev-master" + "version": "v2.2.1" }, "jbtronics/dompdf-font-loader-bundle": { - "version": "dev-main" + "version": "v1.1.1" + }, + "jbtronics/settings-bundle": { + "version": "2.0.1" + }, + "jbtronics/translation-editor-bundle": { + "version": "v1.0" }, "knpuniversity/oauth2-client-bundle": { "version": "2.15", @@ -192,9 +166,6 @@ "./config/packages/knpu_oauth2_client.yaml" ] }, - "laminas/laminas-code": { - "version": "3.4.1" - }, "league/html-to-markdown": { "version": "4.8.2" }, @@ -218,22 +189,31 @@ "version": "1.24.0" }, "nbgrp/onelogin-saml-bundle": { - "version": "v1.3.2" + "version": "v1.4.0" }, - "nelmio/security-bundle": { - "version": "2.4", + "nelmio/cors-bundle": { + "version": "2.3", "recipe": { "repo": "github.com/symfony/recipes", - "branch": "master", - "version": "2.4", - "ref": "65726efb67ff51d89de38195bc0d230fa811f64d" + "branch": "main", + "version": "1.5", + "ref": "6bea22e6c564fba3a1391615cada1437d0bde39c" }, "files": [ - "./config/packages/nelmio_security.yaml" + "./config/packages/nelmio_cors.yaml" ] }, - "netresearch/jsonmapper": { - "version": "v1.6.0" + "nelmio/security-bundle": { + "version": "3.5", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.4", + "ref": "71045833e4f882ad9de8c95fe47efb99a1eec2f7" + }, + "files": [ + "config/packages/nelmio_security.yaml" + ] }, "nikic/php-parser": { "version": "v4.2.1" @@ -241,9 +221,6 @@ "nikolaposa/version": { "version": "2.2.2" }, - "nyholm/nsa": { - "version": "1.1.0" - }, "nyholm/psr7": { "version": "1.0", "recipe": { @@ -268,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", @@ -295,30 +269,6 @@ "php-http/promise": { "version": "v1.0.0" }, - "php-translation/common": { - "version": "1.0.0" - }, - "php-translation/extractor": { - "version": "1.7.1" - }, - "php-translation/symfony-bundle": { - "version": "0.12", - "recipe": { - "repo": "github.com/symfony/recipes-contrib", - "branch": "master", - "version": "0.10", - "ref": "f3ca4e4da63897d177e58da78626c20648c0e102" - }, - "files": [ - "config/packages/dev/php_translation.yaml", - "config/packages/php_translation.yaml", - "config/routes/dev/php_translation.yaml", - "config/routes/php_translation.yaml" - ] - }, - "php-translation/symfony-storage": { - "version": "1.0.1" - }, "phpdocumentor/reflection-common": { "version": "1.0.1" }, @@ -332,7 +282,16 @@ "version": "1.0.3" }, "phpstan/phpstan": { - "version": "0.12.8" + "version": "1.10", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.0", + "ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767" + }, + "files": [ + "phpstan.dist.neon" + ] }, "phpstan/phpstan-doctrine": { "version": "0.12.9" @@ -340,8 +299,20 @@ "phpstan/phpstan-symfony": { "version": "0.12.4" }, - "psalm/plugin-symfony": { - "version": "v1.2.1" + "phpunit/phpunit": { + "version": "11.5", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "11.1", + "ref": "1117deb12541f35793eec9fff7494d7aa12283fc" + }, + "files": [ + ".env.test", + "bin/phpunit", + "phpunit.xml.dist", + "tests/bootstrap.php" + ] }, "psr/cache": { "version": "1.0.1" @@ -402,10 +373,7 @@ "version": "3.0.2" }, "shivas/versioning-bundle": { - "version": "3.1.3" - }, - "spomky-labs/cbor-bundle": { - "version": "v2.0.3" + "version": "4.0.3" }, "symfony/apache-pack": { "version": "1.0", @@ -413,7 +381,7 @@ "repo": "github.com/symfony/recipes-contrib", "branch": "main", "version": "1.0", - "ref": "efb318193e48384eb5c5aadff15396ed698f8ffc" + "ref": "5d454ec6cc4c700ed3d963f3803e1d427d9669fb" }, "files": [ "public/.htaccess" @@ -435,15 +403,15 @@ "version": "v4.2.3" }, "symfony/console": { - "version": "5.3", + "version": "6.4", "recipe": { "repo": "github.com/symfony/recipes", - "branch": "master", + "branch": "main", "version": "5.3", - "ref": "da0c8be8157600ad34f10ff0c9cc91232522e047" + "ref": "1781ff40d8a17d87cf53f8d4cf0c8346ed2bb461" }, "files": [ - "./bin/console" + "bin/console" ] }, "symfony/css-selector": { @@ -495,29 +463,40 @@ "version": "v4.2.3" }, "symfony/flex": { - "version": "1.19", + "version": "2.4", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "1.0", - "ref": "146251ae39e06a95be0fe3d13c807bcf3938b172" + "version": "2.4", + "ref": "52e9754527a15e2b79d9a610f98185a1fe46622a" }, "files": [ - ".env" + ".env", + ".env.dev" ] }, "symfony/form": { - "version": "v4.2.3" - }, - "symfony/framework-bundle": { - "version": "6.2", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "6.2", - "ref": "af47254c5e4cd543e6af3e4508298ffebbdaddd3" + "version": "7.2", + "ref": "7d86a6723f4a623f59e2bf966b6aad2fc461d36b" }, "files": [ + "./config/packages/csrf.yaml" + ] + }, + "symfony/framework-bundle": { + "version": "7.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.4", + "ref": "09f6e081c763a206802674ce0cb34a022f0ffc6d" + }, + "files": [ + ".editorconfig", "config/packages/cache.yaml", "config/packages/framework.yaml", "config/preload.php", @@ -544,12 +523,12 @@ "version": "v4.2.3" }, "symfony/mailer": { - "version": "5.4", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", "version": "4.3", - "ref": "2bf89438209656b85b9a49238c4467bff1b1f939" + "ref": "09051cfde49476e3c12cd3a0e44289ace1c75a4f" }, "files": [ "config/packages/mailer.yaml" @@ -571,15 +550,15 @@ "version": "v4.4.2" }, "symfony/monolog-bundle": { - "version": "3.7", + "version": "3.11", "recipe": { "repo": "github.com/symfony/recipes", - "branch": "master", + "branch": "main", "version": "3.7", - "ref": "213676c4ec929f046dfde5ea8e97625b81bc0578" + "ref": "1b9efb10c54cb51c713a9391c9300ff8bceda459" }, "files": [ - "./config/packages/monolog.yaml" + "config/packages/monolog.yaml" ] }, "symfony/options-resolver": { @@ -589,19 +568,14 @@ "version": "v5.3.8" }, "symfony/phpunit-bridge": { - "version": "5.4", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "5.3", - "ref": "819d3d2ffa4590eba0b8f4f3e5e89415ee4e45c3" + "version": "7.3", + "ref": "dc13fec96bd527bd399c3c01f0aab915c67fd544" }, - "files": [ - ".env.test", - "bin/phpunit", - "phpunit.xml.dist", - "tests/bootstrap.php" - ] + "files": [] }, "symfony/polyfill-ctype": { "version": "v1.14.0" @@ -618,15 +592,6 @@ "symfony/polyfill-intl-normalizer": { "version": "v1.17.0" }, - "symfony/polyfill-mbstring": { - "version": "v1.10.0" - }, - "symfony/polyfill-php72": { - "version": "v1.10.0" - }, - "symfony/polyfill-php80": { - "version": "v1.17.0" - }, "symfony/process": { "version": "v4.2.3" }, @@ -634,18 +599,24 @@ "version": "v4.2.3" }, "symfony/property-info": { - "version": "v4.2.3" - }, - "symfony/proxy-manager-bridge": { - "version": "v5.2.1" - }, - "symfony/routing": { - "version": "6.2", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "6.2", - "ref": "e0a11b4ccb8c9e70b574ff5ad3dfdcd41dec5aa6" + "version": "7.3", + "ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7" + }, + "files": [ + "./config/packages/property_info.yaml" + ] + }, + "symfony/routing": { + "version": "7.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.4", + "ref": "bc94c4fd86f393f3ab3947c18b830ea343e51ded" }, "files": [ "config/packages/routing.yaml", @@ -656,15 +627,16 @@ "version": "v5.3.4" }, "symfony/security-bundle": { - "version": "6.2", + "version": "7.4", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "6.0", - "ref": "8a5b112826f7d3d5b07027f93786ae11a1c7de48" + "version": "7.4", + "ref": "c42fee7802181cdd50f61b8622715829f5d2335c" }, "files": [ - "config/packages/security.yaml" + "config/packages/security.yaml", + "config/routes/security.yaml" ] }, "symfony/security-core": { @@ -683,17 +655,18 @@ "version": "v1.1.5" }, "symfony/stimulus-bundle": { - "version": "2.9", + "version": "2.31", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "2.9", - "ref": "05c45071c7ecacc1e48f94bc43c1f8d4405fb2b2" + "version": "2.24", + "ref": "3357f2fa6627b93658d8e13baa416b2a94a50c5f" }, "files": [ - "./assets/bootstrap.js", - "./assets/controllers.json", - "./assets/controllers/hello_controller.js" + "assets/controllers.json", + "assets/controllers/csrf_protection_controller.js", + "assets/controllers/hello_controller.js", + "assets/stimulus_bootstrap.js" ] }, "symfony/stopwatch": { @@ -703,16 +676,16 @@ "version": "v5.1.0" }, "symfony/translation": { - "version": "5.3", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", - "branch": "master", - "version": "5.3", - "ref": "da64f5a2b6d96f5dc24914517c0350a5f91dee43" + "branch": "main", + "version": "6.3", + "ref": "620a1b84865ceb2ba304c8f8bf2a185fbf32a843" }, "files": [ - "./config/packages/translation.yaml", - "./translations/.gitignore" + "config/packages/translation.yaml", + "translations/.gitignore" ] }, "symfony/translation-contracts": { @@ -722,29 +695,27 @@ "version": "v4.2.3" }, "symfony/twig-bundle": { - "version": "6.3", + "version": "6.4", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "6.3", - "ref": "b7772eb20e92f3fb4d4fe756e7505b4ba2ca1a2c" + "version": "6.4", + "ref": "cab5fd2a13a45c266d45a7d9337e28dee6272877" }, "files": [ - "config/packages/twig.yaml", - "templates/base.html.twig" + "./config/packages/twig.yaml", + "./templates/base.html.twig" ] }, "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", @@ -762,15 +733,24 @@ ] }, "symfony/ux-turbo": { - "version": "v2.0.1" - }, - "symfony/validator": { - "version": "5.4", + "version": "2.28", "recipe": { "repo": "github.com/symfony/recipes", - "branch": "master", - "version": "5.3", - "ref": "c32cfd98f714894c4f128bb99aa2530c1227603c" + "branch": "main", + "version": "2.20", + "ref": "287f7c6eb6e9b65e422d34c00795b360a787380b" + }, + "files": [ + "config/packages/ux_turbo.yaml" + ] + }, + "symfony/validator": { + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.0", + "ref": "8c1c4e28d26a124b0bb273f537ca8ce443472bfd" }, "files": [ "config/packages/validator.yaml" @@ -786,12 +766,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", @@ -799,18 +779,15 @@ ] }, "symfony/webpack-encore-bundle": { - "version": "1.17", + "version": "2.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "1.10", - "ref": "eff2e505d4557c967b6710fe06bd947ba555cae5" + "version": "2.0", + "ref": "719f6110345acb6495e496601fc1b4977d7102b3" }, "files": [ "assets/app.js", - "assets/bootstrap.js", - "assets/controllers.json", - "assets/controllers/hello_controller.js", "assets/styles/app.css", "config/packages/webpack_encore.yaml", "package.json", @@ -820,9 +797,6 @@ "symfony/yaml": { "version": "v4.2.3" }, - "symplify/easy-coding-standard": { - "version": "v7.1.3" - }, "tecnickcom/tc-lib-barcode": { "version": "1.15.20" }, @@ -836,7 +810,7 @@ "version": "v3.0.0" }, "twig/extra-bundle": { - "version": "v3.0.0" + "version": "v3.8.0" }, "twig/html-extra": { "version": "v3.0.3" @@ -856,17 +830,18 @@ "ua-parser/uap-php": { "version": "v3.9.8" }, - "vimeo/psalm": { - "version": "3.5.1" - }, "web-auth/webauthn-symfony-bundle": { - "version": "3.3", + "version": "4.7", "recipe": { "repo": "github.com/symfony/recipes-contrib", "branch": "main", "version": "3.0", - "ref": "9926090a80c2cceeffe96e6c3312b397ea55d4a7" - } + "ref": "a5dff33bd46575bea263af94069650af7742dcb6" + }, + "files": [ + "config/packages/webauthn.yaml", + "config/routes/webauthn_routes.yaml" + ] }, "webmozart/assert": { "version": "1.4.0" diff --git a/templates/_navbar.html.twig b/templates/_navbar.html.twig index 164848f1..446ccdab 100644 --- a/templates/_navbar.html.twig +++ b/templates/_navbar.html.twig @@ -1,15 +1,24 @@ {% import "helper.twig" as helper %} +{% import "vars.macro.twig" as vars %} +{% import "components/search.macro.html.twig" as search %}
    diff --git a/templates/admin/_delete_form.html.twig b/templates/admin/_delete_form.html.twig index 762b91b6..fd653256 100644 --- a/templates/admin/_delete_form.html.twig +++ b/templates/admin/_delete_form.html.twig @@ -6,7 +6,7 @@
    - {% set delete_disabled = (not is_granted("delete", entity)) or (entity.group is defined and entity.id == 1) %} + {% set delete_disabled = (not is_granted("delete", entity)) or (entity.group is defined and entity.id == 1) or entity == app.user %}
    -{% endblock %} \ No newline at end of file + +
    + {{ form_row(form.eda_info.reference_prefix) }} + +
    +
    + {{ form_row(form.eda_info.visibility) }} +
    +
    + +
    +
    + {{ form_widget(form.eda_info.exclude_from_bom) }} + {{ form_widget(form.eda_info.exclude_from_board) }} + {{ form_widget(form.eda_info.exclude_from_sim) }} +
    +
    + +
    +
    +
    {% trans %}eda_info.kicad_section.title{% endtrans %}:
    +
    +
    + {{ form_row(form.eda_info.kicad_symbol) }} +
    +{% endblock %} diff --git a/templates/admin/currency_admin.html.twig b/templates/admin/currency_admin.html.twig index dca46ce6..a5d59970 100644 --- a/templates/admin/currency_admin.html.twig +++ b/templates/admin/currency_admin.html.twig @@ -1,7 +1,9 @@ {% extends "admin/base_admin.html.twig" %} +{% import "vars.macro.twig" as vars %} + {% block card_title %} - {% trans %}currency.caption{% endtrans %} + {{ type_label_p(entity) }} {% endblock %} {% block additional_controls %} @@ -20,8 +22,8 @@ {{ form_row(form.exchange_rate) }} {% if entity.inverseExchangeRate %}

    - {{ '1'|format_currency(default_currency) }} = {{ entity.inverseExchangeRate.tofloat | format_currency(entity.isoCode, {fraction_digit: 5}) }}
    - {{ '1'|format_currency(entity.isoCode) }} = {{ entity.exchangeRate.tofloat | format_currency(default_currency, {fraction_digit: 5}) }} + {{ '1'|format_currency(vars.base_currency()) }} = {{ entity.inverseExchangeRate.tofloat | format_currency(entity.isoCode, {fraction_digit: 5}) }}
    + {{ '1'|format_currency(entity.isoCode) }} = {{ entity.exchangeRate.tofloat | format_currency(vars.base_currency(), {fraction_digit: 5}) }}

    {% endif %} @@ -39,4 +41,4 @@ {% block new_title %} {% trans %}currency.new{% endtrans %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/admin/footprint_admin.html.twig b/templates/admin/footprint_admin.html.twig index e4ed7713..1ed39e9f 100644 --- a/templates/admin/footprint_admin.html.twig +++ b/templates/admin/footprint_admin.html.twig @@ -1,7 +1,7 @@ {% extends "admin/base_admin.html.twig" %} {% block card_title %} - {% trans %}footprint.labelp{% endtrans %} + {{ type_label_p(entity) }} {% endblock %} {% block master_picture_block %} @@ -19,4 +19,19 @@ {% block additional_controls %} {{ form_row(form.alternative_names) }} -{% endblock %} \ No newline at end of file +{% endblock %} + +{% block additional_pills %} + +{% endblock %} + +{% block additional_panes %} +
    +
    +
    +
    {% trans %}eda_info.kicad_section.title{% endtrans %}:
    +
    +
    + {{ form_row(form.eda_info.kicad_footprint) }} +
    +{% endblock %} diff --git a/templates/admin/group_admin.html.twig b/templates/admin/group_admin.html.twig index 91975524..831c08d5 100644 --- a/templates/admin/group_admin.html.twig +++ b/templates/admin/group_admin.html.twig @@ -1,7 +1,7 @@ {% extends "admin/base_admin.html.twig" %} {% block card_title %} - {% trans %}group.edit.caption{% endtrans %} + {{ type_label_p(entity) }} {% endblock %} @@ -27,4 +27,4 @@ {% block new_title %} {% trans %}group.new{% endtrans %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/admin/label_profile_admin.html.twig b/templates/admin/label_profile_admin.html.twig index 10c2320f..8702b18a 100644 --- a/templates/admin/label_profile_admin.html.twig +++ b/templates/admin/label_profile_admin.html.twig @@ -1,7 +1,7 @@ {% extends "admin/base_admin.html.twig" %} {% block card_title %} - {% trans %}label_profile.caption{% endtrans %} + {{ type_label_p(entity) }} {% endblock %} {% block additional_pills %} @@ -58,4 +58,4 @@ {% block new_title %} {% trans %}label_profile.new{% endtrans %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/admin/manufacturer_admin.html.twig b/templates/admin/manufacturer_admin.html.twig index 5db892c0..4f8f1c2b 100644 --- a/templates/admin/manufacturer_admin.html.twig +++ b/templates/admin/manufacturer_admin.html.twig @@ -1,7 +1,7 @@ {% extends "admin/base_company_admin.html.twig" %} {% block card_title %} - {% trans %}manufacturer.caption{% endtrans %} + {{ type_label_p(entity) }} {% endblock %} {% block edit_title %} @@ -10,4 +10,4 @@ {% block new_title %} {% trans %}manufacturer.new{% endtrans %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/admin/measurement_unit_admin.html.twig b/templates/admin/measurement_unit_admin.html.twig index 31748509..14df7364 100644 --- a/templates/admin/measurement_unit_admin.html.twig +++ b/templates/admin/measurement_unit_admin.html.twig @@ -1,7 +1,7 @@ {% extends "admin/base_admin.html.twig" %} {% block card_title %} - {% trans %}measurement_unit.caption{% endtrans %} + {{ type_label_p(entity) }} {% endblock %} {% block edit_title %} diff --git a/templates/admin/part_custom_state_admin.html.twig b/templates/admin/part_custom_state_admin.html.twig new file mode 100644 index 00000000..9d857646 --- /dev/null +++ b/templates/admin/part_custom_state_admin.html.twig @@ -0,0 +1,14 @@ +{% extends "admin/base_admin.html.twig" %} + +{% block card_title %} + {{ type_label_p(entity) }} +{% endblock %} + +{% block edit_title %} + {% trans %}part_custom_state.edit{% endtrans %}: {{ entity.name }} +{% endblock %} + +{% block new_title %} + {% trans %}part_custom_state.new{% endtrans %} +{% endblock %} + diff --git a/templates/admin/project_admin.html.twig b/templates/admin/project_admin.html.twig index 4ed0849a..d199b63c 100644 --- a/templates/admin/project_admin.html.twig +++ b/templates/admin/project_admin.html.twig @@ -3,7 +3,7 @@ {# @var entity App\Entity\ProjectSystem\Project #} {% block card_title %} - {% trans %}project.caption{% endtrans %} + {{ type_label_p(entity) }} {% endblock %} {% block edit_title %} @@ -36,7 +36,7 @@ {% if entity.buildPart %} {{ entity.buildPart.name }} {% else %} - {% trans %}project.edit.associated_build_part.add{% endtrans %} {% endif %}

    {% trans %}project.edit.associated_build.hint{% endtrans %}

    @@ -59,4 +59,4 @@ {% endif %}
    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/admin/storelocation_admin.html.twig b/templates/admin/storelocation_admin.html.twig index c93339dc..b01ecc73 100644 --- a/templates/admin/storelocation_admin.html.twig +++ b/templates/admin/storelocation_admin.html.twig @@ -2,7 +2,7 @@ {% import "label_system/dropdown_macro.html.twig" as dropdown %} {% block card_title %} - {% trans %}storelocation.labelp{% endtrans %} + {{ type_label_p(entity) }} {% endblock %} {% block additional_controls %} @@ -38,4 +38,4 @@ {% block new_title %} {% trans %}storelocation.new{% endtrans %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/admin/supplier_admin.html.twig b/templates/admin/supplier_admin.html.twig index ce38a5ca..d0ca85aa 100644 --- a/templates/admin/supplier_admin.html.twig +++ b/templates/admin/supplier_admin.html.twig @@ -1,7 +1,7 @@ {% extends "admin/base_company_admin.html.twig" %} {% block card_title %} - {% trans %}supplier.caption{% endtrans %} + {{ type_label_p(entity) }} {% endblock %} {% block additional_panes %} @@ -19,4 +19,4 @@ {% block new_title %} {% trans %}supplier.new{% endtrans %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/admin/user_admin.html.twig b/templates/admin/user_admin.html.twig index 772b42d9..9b241e56 100644 --- a/templates/admin/user_admin.html.twig +++ b/templates/admin/user_admin.html.twig @@ -5,7 +5,7 @@ {# @var entity \App\Entity\UserSystem\User #} {% block card_title %} - {% trans %}user.edit.caption{% endtrans %} + {{ type_label_p(entity) }} {% endblock %} {% block comment %}{% endblock %} @@ -111,4 +111,4 @@ {% block preview_picture %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/attachment_list.html.twig b/templates/attachment_list.html.twig index abb6f4ad..3ff45700 100644 --- a/templates/attachment_list.html.twig +++ b/templates/attachment_list.html.twig @@ -34,6 +34,8 @@ {{ form_row(filterForm.attachmentType) }} {{ form_row(filterForm.targetType) }} {{ form_row(filterForm.showInTable) }} + {{ form_row(filterForm.originalFileName) }} + {{ form_row(filterForm.externalLink) }} {{ form_row(filterForm.lastModified) }} {{ form_row(filterForm.addedDate) }} {{ form_row(filterForm.dbId) }} diff --git a/templates/base.html.twig b/templates/base.html.twig index 7914a9a7..2db726ee 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -1,5 +1,9 @@ +{% import "vars.macro.twig" as vars %} + - + @@ -17,8 +21,10 @@ + {# Turbo control headers #} - + + @@ -26,21 +32,20 @@ - {% apply trim %}{% block title %}{{ partdb_title}}{% endblock %}{% endapply %} - {% set current_page_title = block("title") %} + {# The content block is already escaped. so we must not escape it again. #} + {% apply trim|raw %}{% block title %}{{ vars.partdb_title() }}{% endblock %}{% endapply %} + {% set current_page_title = block("title")|raw %} {% block stylesheets %} {# Include the main bootstrap theme based on user/global setting #} - {% if not app.user.theme is defined %} - {% set theme = global_theme %} + {% if app.user.theme is not defined or app.user.theme is null %} + {% set theme = settings_instance('customization').theme %} {% else %} {% set theme = app.user.theme %} {% endif %} - - {% if theme and theme in available_themes and encore_entry_exists('theme_' ~ theme) %} {{ encore_entry_link_tags('theme_' ~ theme) }} {% else %} @@ -48,17 +53,19 @@ {% endif %} {{ encore_entry_link_tags('app') }} + + {% set table_settings = settings_instance('table') %} + {% endblock %} {% block javascripts %} {{ encore_entry_script_tags('app') }} {{ encore_entry_script_tags('webauthn_tfa') }} - - {# load translation files for ckeditor #} - {% set two_chars_locale = app.request.locale|default("en")|slice(0,2) %} - {% if two_chars_locale != "en" %} - - {% endif %} {% endblock %} @@ -106,14 +113,14 @@ {# Back to top buton #} - {# Must be outside of the sidebar or it will be hidden too #} - diff --git a/templates/bundles/TwigBundle/Exception/assets/error.css.twig b/templates/bundles/TwigBundle/Exception/assets/error.css.twig index 0d0a1be6..ffd9e7e9 100644 --- a/templates/bundles/TwigBundle/Exception/assets/error.css.twig +++ b/templates/bundles/TwigBundle/Exception/assets/error.css.twig @@ -59,7 +59,7 @@ ul { .footer { box-sizing: border-box; - position: absolute; + position: fixed; bottom: 0; width: 100%; height: 60px; diff --git a/templates/bundles/TwigBundle/Exception/error.html.twig b/templates/bundles/TwigBundle/Exception/error.html.twig index eb3da877..efdba462 100644 --- a/templates/bundles/TwigBundle/Exception/error.html.twig +++ b/templates/bundles/TwigBundle/Exception/error.html.twig @@ -37,7 +37,7 @@
  • Run php bin/console cache:clear to clear cache
  • {% endblock %} -

    If you think that this is an error in Part-DB, open an Issue on GitHub.

    +

    If you think that this is an error in Part-DB, open an Issue on GitHub.

    You can disable these hints in config/parameters.yaml by setting partdb.error_pages.show_help to false.

    {% endif %} @@ -46,7 +46,7 @@
    {% block footer %} - This page was generated by Part-DB on {{ "now" | format_datetime("medium", "short") }} . + This page was generated by Part-DB on {{ "now" | format_datetime("medium", "short") }} . {% endblock %}
    diff --git a/templates/bundles/TwigBundle/Exception/error403.html.twig b/templates/bundles/TwigBundle/Exception/error403.html.twig index f5987179..334670fc 100644 --- a/templates/bundles/TwigBundle/Exception/error403.html.twig +++ b/templates/bundles/TwigBundle/Exception/error403.html.twig @@ -1,6 +1,9 @@ {% extends "bundles/TwigBundle/Exception/error.html.twig" %} {% block status_comment %} - Nice try! But you are not allowed to do this! + Nice try! But you are not allowed to do this!
    + {{ exception.message }}
    If you think you should have access to this ressource, contact the adminstrator. -{% endblock %} \ No newline at end of file + + +{% endblock %} diff --git a/templates/components/attachments.macro.html.twig b/templates/components/attachments.macro.html.twig index d7daf2ee..96482d75 100644 --- a/templates/components/attachments.macro.html.twig +++ b/templates/components/attachments.macro.html.twig @@ -7,15 +7,22 @@ {% for attachment in form %} - {{ form_widget(attachment) }} + {{ form_widget(attachment) }} {% endfor %}
    - +
    + + + +
    {% endmacro %} diff --git a/templates/components/collection_type.macro.html.twig b/templates/components/collection_type.macro.html.twig index 1db04763..fde2b961 100644 --- a/templates/components/collection_type.macro.html.twig +++ b/templates/components/collection_type.macro.html.twig @@ -29,4 +29,13 @@ {% macro delete_btn() %} {{ stimulus_action('elements/collection_type', 'deleteElement') }} +{% endmacro %} + +{% macro new_element_indicator(value) %} + {% if value.id is not defined or value.id is null %} + + New alerts + + {% endif %} {% endmacro %} \ No newline at end of file diff --git a/templates/components/datatables.macro.html.twig b/templates/components/datatables.macro.html.twig index 9c88e461..d7873498 100644 --- a/templates/components/datatables.macro.html.twig +++ b/templates/components/datatables.macro.html.twig @@ -1,5 +1,5 @@ {% macro datatable(datatable, controller = 'elements/datatables/datatables', state_save_tag = null) %} -
    +
    @@ -19,18 +19,17 @@ {% macro partsDatatableWithForm(datatable, state_save_tag = 'parts') %}
    - + -
    - {# #} - +
    @@ -40,7 +39,7 @@ @@ -93,4 +97,4 @@
    -{% endmacro %} \ No newline at end of file +{% endmacro %} diff --git a/templates/components/history_log_macros.html.twig b/templates/components/history_log_macros.html.twig index f3481305..68df2015 100644 --- a/templates/components/history_log_macros.html.twig +++ b/templates/components/history_log_macros.html.twig @@ -17,7 +17,7 @@ {{ stimulus_controller('elements/delete_btn') }} {{ stimulus_action('elements/delete_btn', "submit", "submit") }} data-delete-title="{% trans %}log.undo.confirm_title{% endtrans %}" data-delete-message="{% trans %}log.undo.confirm_message{% endtrans %}"> - + {{ datatables.logDataTable(datatable, tag) }} diff --git a/templates/_navbar_search.html.twig b/templates/components/search.macro.html.twig similarity index 58% rename from templates/_navbar_search.html.twig rename to templates/components/search.macro.html.twig index 308d6229..e62af2b1 100644 --- a/templates/_navbar_search.html.twig +++ b/templates/components/search.macro.html.twig @@ -1,74 +1,105 @@ - \ No newline at end of file +{# Render a complete usable search form including the form tags. mode can be "standalone" or "navbar" #} +{% macro search_form(mode = "standalone") %} + {% set is_navbar = (mode == "navbar") %} + +
    + + {# Show the options left in navbar #} + {% if is_navbar %} + {{ _self.settings_drodown(is_navbar) }} + {% endif %} + +
    + + +
    + + {# And right in the standalone mode #} + {% if not is_navbar %} + {{ _self.settings_drodown(is_navbar) }} + {% endif %} +
    +{% endmacro %} \ No newline at end of file diff --git a/templates/components/tree_macros.html.twig b/templates/components/tree_macros.html.twig index 12bef78f..aaa871ea 100644 --- a/templates/components/tree_macros.html.twig +++ b/templates/components/tree_macros.html.twig @@ -1,13 +1,15 @@ {% macro sidebar_dropdown() %} + {% set currentLocale = app.request.locale %} + {# Format is [mode, route, label, show_condition] #} {% set data_sources = [ - ['categories', path('tree_category_root'), 'category.labelp', is_granted('@categories.read') and is_granted('@parts.read')], - ['locations', path('tree_location_root'), 'storelocation.labelp', is_granted('@storelocations.read') and is_granted('@parts.read')], - ['footprints', path('tree_footprint_root'), 'footprint.labelp', is_granted('@footprints.read') and is_granted('@parts.read')], - ['manufacturers', path('tree_manufacturer_root'), 'manufacturer.labelp', is_granted('@manufacturers.read') and is_granted('@parts.read')], - ['suppliers', path('tree_supplier_root'), 'supplier.labelp', is_granted('@suppliers.read') and is_granted('@parts.read')], - ['devices', path('tree_device_root'), 'project.labelp', is_granted('@projects.read')], - ['tools', path('tree_tools'), 'tools.label', true], + ['categories', path('tree_category_root'), '@category@@', is_granted('@categories.read') and is_granted('@parts.read')], + ['locations', path('tree_location_root'), '@storage_location@@', is_granted('@storelocations.read') and is_granted('@parts.read'), ], + ['footprints', path('tree_footprint_root'), '@footprint@@', is_granted('@footprints.read') and is_granted('@parts.read')], + ['manufacturers', path('tree_manufacturer_root'), '@manufacturer@@', is_granted('@manufacturers.read') and is_granted('@parts.read'), 'manufacturer'], + ['suppliers', path('tree_supplier_root'), '@supplier@@', is_granted('@suppliers.read') and is_granted('@parts.read'), 'supplier'], + ['projects', path('tree_device_root'), '@project@@', is_granted('@projects.read'), 'project'], + ['tools', path('tree_tools'), 'tools.label', true, 'tool'], ] %} @@ -18,9 +20,20 @@ {% for source in data_sources %} {% if source[3] %} {# show_condition #} -
  • +
  • + {% if source[2] starts with '@' %} + {% set label = type_label_p(source[2]|replace({'@': ''})) %} + {% else %} + {% set label = source[2]|trans %} + {% endif %} + + +
  • {% endif %} {% endfor %} {% endmacro %} @@ -61,4 +74,4 @@
    -{% endmacro %} \ No newline at end of file +{% endmacro %} diff --git a/templates/form/collection_types_layout.html.twig b/templates/form/collection_types_layout.html.twig index 9cc1297b..96b71bf0 100644 --- a/templates/form/collection_types_layout.html.twig +++ b/templates/form/collection_types_layout.html.twig @@ -50,8 +50,9 @@ {{ form_errors(form.name) }} - {{ form_errors(form) }} diff --git a/templates/form/extended_bootstrap_layout.html.twig b/templates/form/extended_bootstrap_layout.html.twig index 811f57ac..75e44a15 100644 --- a/templates/form/extended_bootstrap_layout.html.twig +++ b/templates/form/extended_bootstrap_layout.html.twig @@ -1,5 +1,9 @@ {% extends 'bootstrap_5_horizontal_layout.html.twig' %} +{%- block toggle_password_widget -%} +
    {{ block('password_widget') }}
    +{%- endblock toggle_password_widget -%} + {# Make form rows smaller #} {% block form_row -%} {%- set row_attr = row_attr|merge({"class": "mb-2"}) -%} @@ -139,4 +143,4 @@ {% else %} {{- parent() -}} {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/form/filter_types_layout.html.twig b/templates/form/filter_types_layout.html.twig index e99e6b62..cae9e3ea 100644 --- a/templates/form/filter_types_layout.html.twig +++ b/templates/form/filter_types_layout.html.twig @@ -54,7 +54,7 @@ {{ form_widget(form.name, {"attr": {"data-pages--parameters-autocomplete-target": "name"}}) }} {{ form_widget(form.symbol, {"attr": {"data-pages--parameters-autocomplete-target": "symbol", "data-pages--latex-preview-target": "input"}}) }} {{ form_widget(form.value) }} - {{ form_widget(form.unit, {"attr": {"data-pages--parameters-autocomplete-target": "unit", "data-pages--latex-preview-target": "input"}}) }} + {{ form_widget(form.unit, {"attr": {"data-pages--parameters-autocomplete-target": "unit", "data-pages--latex-preview-target": "input"}}) }} {{ form_widget(form.value_text) }}
    @@ -89,6 +91,10 @@ {% endif %}
    + {% if show_dependency_notice %} + {% trans %}permission.legend.dependency_note{% endtrans %} + {% endif %} +
    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/form/settings_form.html.twig b/templates/form/settings_form.html.twig new file mode 100644 index 00000000..8f76720e --- /dev/null +++ b/templates/form/settings_form.html.twig @@ -0,0 +1,25 @@ +{% extends "form/extended_bootstrap_layout.html.twig" %} + +{% block form_label %} + {# If parameter_envvar exists on form then show it as tooltip #} + {% if parameter_envvar is defined and parameter_envvar is not null %} + {%- set label_attr = label_attr|merge({title: t('settings.tooltip.overrideable_by_env', {'%env%': (parameter_envvar)|trim})}) -%} + {% endif %} + {{- parent() -}} +{% endblock %} + +{% block checkbox_radio_label %} + {# If parameter_envvar exists on form then show it as tooltip #} + {% if parameter_envvar is defined and parameter_envvar is not null %} + {%- set label_attr = label_attr|merge({title: t('settings.tooltip.overrideable_by_env', {'%env%': (parameter_envvar)|trim})}) -%} + {% endif %} + {{- parent() -}} +{% endblock %} + +{% block tristate_label %} + {# If parameter_envvar exists on form then show it as tooltip #} + {% if parameter_envvar is defined and parameter_envvar is not null %} + {%- set label_attr = label_attr|merge({title: t('settings.tooltip.overrideable_by_env', {'%env%': (parameter_envvar)|trim})}) -%} + {% endif %} + {{- parent() -}} +{% endblock %} diff --git a/templates/form/synonyms_collection.html.twig b/templates/form/synonyms_collection.html.twig new file mode 100644 index 00000000..ee69dffc --- /dev/null +++ b/templates/form/synonyms_collection.html.twig @@ -0,0 +1,59 @@ +{% macro renderForm(child) %} +
    + {% form_theme child "form/vertical_bootstrap_layout.html.twig" %} +
    +
    {{ form_row(child.dataSource) }}
    +
    {{ form_row(child.locale) }}
    +
    {{ form_row(child.translation_singular) }}
    +
    {{ form_row(child.translation_plural) }}
    +
    + +
    +
    +
    +{% endmacro %} + +{% block type_synonyms_collection_widget %} + {% set _attrs = attr|default({}) %} + {% set _attrs = _attrs|merge({ + class: (_attrs.class|default('') ~ ' type_synonyms_collection-widget')|trim + }) %} + + {% set has_proto = prototype is defined %} + {% if has_proto %} + {% set __proto %} + {{- _self.renderForm(prototype) -}} + {% endset %} + {% set _proto_html = __proto|e('html_attr') %} + {% set _proto_name = form.vars.prototype_name|default('__name__') %} + {% set _index = form|length %} + {% endif %} + +
    +
    +
    {% trans%}settings.synonyms.type_synonym.type{% endtrans%}
    +
    {% trans%}settings.synonyms.type_synonym.language{% endtrans%}
    +
    {% trans%}settings.synonyms.type_synonym.translation_singular{% endtrans%}
    +
    {% trans%}settings.synonyms.type_synonym.translation_plural{% endtrans%}
    +
    +
    + +
    + {% for child in form %} + {{ _self.renderForm(child) }} + {% endfor %} +
    + +
    +{% endblock %} diff --git a/templates/form/vertical_bootstrap_layout.html.twig b/templates/form/vertical_bootstrap_layout.html.twig new file mode 100644 index 00000000..5f41d82e --- /dev/null +++ b/templates/form/vertical_bootstrap_layout.html.twig @@ -0,0 +1,26 @@ +{% extends 'bootstrap_5_layout.html.twig' %} + + +{%- block choice_widget_collapsed -%} + {# Only add the BS5 form-select class if we dont use bootstrap-selectpicker #} + {# {% if attr["data-controller"] is defined and attr["data-controller"] not in ["elements--selectpicker"] %} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-select')|trim}) -%} + {% else %} + {# If it is an selectpicker add form-control class to fill whole width + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%} + {% endif %} + #} + + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-select')|trim}) -%} + + {# If no data-controller was explictly defined add data-controller=elements--select #} + {% if attr["data-controller"] is not defined %} + {%- set attr = attr|merge({"data-controller": "elements--select"}) -%} + + {% if attr["data-empty-message"] is not defined %} + {%- set attr = attr|merge({"data-empty-message": ("selectpicker.nothing_selected"|trans)}) -%} + {% endif %} + {% endif %} + + {{- block("choice_widget_collapsed", "bootstrap_base_layout.html.twig") -}} +{%- endblock choice_widget_collapsed -%} diff --git a/templates/helper.twig b/templates/helper.twig index d0ea72be..66268a96 100644 --- a/templates/helper.twig +++ b/templates/helper.twig @@ -188,6 +188,15 @@ {% endif %} {% endmacro %} +{% macro part_icon_link(part) %} + {% set preview_attach = part_preview_generator.tablePreviewAttachment(part) %} + {% if preview_attach %} + Part image + {% endif %} + {{ part.name }} +{% endmacro %} + {% macro entity_preview_sm(entity) %} {# @var entity \App\Entity\Contracts\HasMasterAttachmentInterface #} {% if entity.masterPictureAttachment and entity.masterPictureAttachment.picture and attachment_manager.fileExisting(entity.masterPictureAttachment) %} @@ -205,20 +214,30 @@ {% endmacro %} {% macro parameters_table(parameters) %} - +
    + {% for param in parameters %} - - + + + {% endfor %}
    {% trans %}specifications.property{% endtrans %}{% trans %}specifications.symbol{% endtrans %} {% trans %}specifications.value{% endtrans %}
    {{ param.name }} {% if param.symbol is not empty %}${{ param.symbol }}${% endif %}{{ param.formattedValue }}{{ param.name }}{% if param.symbol is not empty %}${{ param.symbol }}${% endif %}{{ param.formattedValue(true) }}
    -{% endmacro parameters_table %} \ No newline at end of file +{% endmacro parameters_table %} + +{% macro format_date_nullable(datetime) %} + {% if datetime is null %} + {% trans %}datetime.never{% endtrans %} + {% else %} + {{ datetime|format_datetime }} + {% endif %} +{% endmacro %} diff --git a/templates/homepage.html.twig b/templates/homepage.html.twig index 138257c7..6e7aa360 100644 --- a/templates/homepage.html.twig +++ b/templates/homepage.html.twig @@ -1,21 +1,26 @@ {% extends "base.html.twig" %} {% import "components/new_version.macro.html.twig" as nv %} +{% import "components/search.macro.html.twig" as search %} +{% import "vars.macro.twig" as vars %} -{% block content %} - - {% if is_granted('@system.show_updates') %} - {{ nv.new_version_alert(new_version_available, new_version, new_version_url) }} +{% block item_search %} + {% if is_granted('@parts.read') %} + {{ search.search_form("standalone") }} {% endif %} +{% endblock %} +{% block item_banner %}
    -

    {{ partdb_title }}

    -

    - {% trans %}version.caption{% endtrans %}: {{ shivas_app_version }} - {% if git_branch is not empty or git_commit is not empty %} - ({{ git_branch ?? '' }}/{{ git_commit ?? '' }}) - {% endif %} -

    +

    {{ vars.partdb_title() }}

    + {% if settings_instance('customization').showVersionOnHomepage %} +

    + {% trans %}version.caption{% endtrans %}: {{ shivas_app_version }} + {% if git_branch is not empty or git_commit is not empty %} + ({{ git_branch ?? '' }}/{{ git_commit ?? '' }}) + {% endif %} +

    + {% endif %} {% if banner is not empty %}
    @@ -23,9 +28,11 @@
    {% endif %}
    +{% endblock %} +{% block item_first_steps %} {% if show_first_steps %} -
    +

    {% trans %}homepage.first_steps.title{% endtrans %}

    @@ -43,8 +50,10 @@
    {% endif %} +{% endblock %} -
    +{% block item_license %} +

    {% trans %}homepage.license{% endtrans %}

    @@ -53,16 +62,18 @@ Jan Böhmer .
    Part-DB is published under the GNU Affero General Public License v3.0 (or later), so it comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. - Click here for details.
    + Click here for details.

    - {% trans %}homepage.github.caption{% endtrans %}: {% trans with {'%href%': 'https://github.com/Part-DB/Part-DB-symfony'}%}homepage.github.text{% endtrans %}
    + {% trans %}homepage.github.caption{% endtrans %}: {% trans with {'%href%': 'https://github.com/Part-DB/Part-DB-server'}%}homepage.github.text{% endtrans %}
    {% trans %}homepage.help.caption{% endtrans %}: {% trans with {'%href%': 'https://docs.part-db.de/'}%}homepage.help.text{% endtrans %}
    - {% trans %}homepage.forum.caption{% endtrans %}: {% trans with {'%href%': 'https://github.com/Part-DB/Part-DB-symfony/discussions'}%}homepage.forum.text{% endtrans %}
    + {% trans %}homepage.forum.caption{% endtrans %}: {% trans with {'%href%': 'https://github.com/Part-DB/Part-DB-server/discussions'}%}homepage.forum.text{% endtrans %}
    +{% endblock %} +{% block item_last_activity %} {% if datatable is not null %} -
    +
    {% trans %}homepage.last_activity{% endtrans %}
    {% import "components/history_log_macros.html.twig" as log %} @@ -70,4 +81,23 @@
    {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} + +{% block content %} + + {% if is_granted('@system.show_updates') %} + {{ nv.new_version_alert(new_version_available, new_version, new_version_url) }} + {% endif %} + + {% for item in settings_instance('customization').homepageitems %} + {% if block('item_' ~ item.value) is defined %} + {{ block('item_' ~ item.value) }} +
    + {% else %} + + {% endif %} + {% endfor %} + +{% endblock %} diff --git a/templates/info_providers/bulk_import/manage.html.twig b/templates/info_providers/bulk_import/manage.html.twig new file mode 100644 index 00000000..9bbed906 --- /dev/null +++ b/templates/info_providers/bulk_import/manage.html.twig @@ -0,0 +1,124 @@ +{% extends "main_card.html.twig" %} + +{% block title %} + {% trans %}info_providers.bulk_import.manage_jobs{% endtrans %} +{% endblock %} + +{% block card_title %} + {% trans %}info_providers.bulk_import.manage_jobs{% endtrans %} +{% endblock %} + +{% block card_content %} + +
    + +
    +

    + {% trans %}info_providers.bulk_import.manage_jobs_description{% endtrans %} +

    +
    + + {% if jobs is not empty %} +
    + + + + + + + + + + + + + + + + {% for job in jobs %} + + + + + + + + + + + + {% endfor %} + +
    {% trans %}info_providers.bulk_import.job_name{% endtrans %}{% trans %}info_providers.bulk_import.parts_count{% endtrans %}{% trans %}info_providers.bulk_import.results_count{% endtrans %}{% trans %}info_providers.bulk_import.progress{% endtrans %}{% trans %}info_providers.bulk_import.status{% endtrans %}{% trans %}info_providers.bulk_import.created_by{% endtrans %}{% trans %}info_providers.bulk_import.created_at{% endtrans %}{% trans %}info_providers.bulk_import.completed_at{% endtrans %}{% trans %}info_providers.bulk_import.action.label{% endtrans %}
    + {{ job.displayNameKey|trans(job.displayNameParams) }} - {{ job.formattedTimestamp }} + {% if job.isInProgress %} + Active + {% endif %} + {{ job.partCount }}{{ job.resultCount }} +
    +
    +
    +
    +
    + {{ job.progressPercentage }}% +
    + + {% trans with {'%current%': job.completedPartsCount + job.skippedPartsCount, '%total%': job.partCount} %}info_providers.bulk_import.progress_label{% endtrans %} + +
    + {% if job.isPending %} + {% trans %}info_providers.bulk_import.status.pending{% endtrans %} + {% elseif job.isInProgress %} + {% trans %}info_providers.bulk_import.status.in_progress{% endtrans %} + {% elseif job.isCompleted %} + {% trans %}info_providers.bulk_import.status.completed{% endtrans %} + {% elseif job.isStopped %} + {% trans %}info_providers.bulk_import.status.stopped{% endtrans %} + {% elseif job.isFailed %} + {% trans %}info_providers.bulk_import.status.failed{% endtrans %} + {% endif %} + {{ job.createdBy.fullName(true) }}{{ job.createdAt|format_datetime('short') }} + {% if job.completedAt %} + {{ job.completedAt|format_datetime('short') }} + {% else %} + - + {% endif %} + +
    + {% if job.isInProgress or job.isCompleted or job.isStopped %} + + {% trans %}info_providers.bulk_import.view_results{% endtrans %} + + {% endif %} + {% if job.canBeStopped %} + + {% endif %} + {% if job.isCompleted or job.isFailed or job.isStopped %} + + {% endif %} +
    +
    +
    + {% else %} + + {% endif %} + +
    + +{% endblock %} diff --git a/templates/info_providers/bulk_import/step1.html.twig b/templates/info_providers/bulk_import/step1.html.twig new file mode 100644 index 00000000..bb9bb351 --- /dev/null +++ b/templates/info_providers/bulk_import/step1.html.twig @@ -0,0 +1,304 @@ +{% extends "main_card.html.twig" %} + +{% import "info_providers/providers.macro.html.twig" as providers_macro %} +{% import "helper.twig" as helper %} + +{% block title %} + {% trans %}info_providers.bulk_import.step1.title{% endtrans %} +{% endblock %} + +{% block card_title %} + {% trans %}info_providers.bulk_import.step1.title{% endtrans %} + {{ parts|length }} {% trans %}info_providers.bulk_import.parts_selected{% endtrans %} +{% endblock %} + +{% block card_content %} + +
    + + + {% if existing_jobs is not empty %} +
    +
    +
    {% trans %}info_providers.bulk_import.existing_jobs{% endtrans %}
    +
    +
    +
    + + + + + + + + + + + + + + {% for job in existing_jobs %} + + + + + + + + + + {% endfor %} + +
    {% trans %}info_providers.bulk_import.job_name{% endtrans %}{% trans %}info_providers.bulk_import.parts_count{% endtrans %}{% trans %}info_providers.bulk_import.results_count{% endtrans %}{% trans %}info_providers.bulk_import.progress{% endtrans %}{% trans %}info_providers.bulk_import.status{% endtrans %}{% trans %}info_providers.bulk_import.created_at{% endtrans %}{% trans %}info_providers.bulk_import.action.label{% endtrans %}
    {{ job.displayNameKey|trans(job.displayNameParams) }} - {{ job.formattedTimestamp }}{{ job.partCount }}{{ job.resultCount }} +
    +
    +
    +
    +
    + {{ job.progressPercentage }}% +
    + {{ job.completedPartsCount }}/{{ job.partCount }} +
    + {% if job.isPending %} + {% trans %}info_providers.bulk_import.status.pending{% endtrans %} + {% elseif job.isInProgress %} + {% trans %}info_providers.bulk_import.status.in_progress{% endtrans %} + {% elseif job.isCompleted %} + {% trans %}info_providers.bulk_import.status.completed{% endtrans %} + {% elseif job.isFailed %} + {% trans %}info_providers.bulk_import.status.failed{% endtrans %} + {% endif %} + {{ job.createdAt|date('Y-m-d H:i') }} + {% if job.isInProgress or job.isCompleted %} + + {% trans %}info_providers.bulk_import.view_results{% endtrans %} + + {% endif %} +
    +
    +
    +
    + {% endif %} + + + + + + + + +
    +
    +
    {% trans %}info_providers.bulk_import.selected_parts{% endtrans %}
    +
    + +
    + + {{ form_start(form) }} + +
    +
    +
    {% trans %}info_providers.bulk_import.field_mappings{% endtrans %}
    + {% trans %}info_providers.bulk_import.field_mappings_help{% endtrans %} +
    +
    + + + + + + + + + + + {% for mapping in form.field_mappings %} + + + + + + + {% endfor %} + +
    {% trans %}info_providers.bulk_search.search_field{% endtrans %}{% trans %}info_providers.bulk_search.providers{% endtrans %}{% trans %}info_providers.bulk_search.priority{% endtrans %}{% trans %}info_providers.bulk_import.actions.label{% endtrans %}
    {{ form_widget(mapping.field) }}{{ form_errors(mapping.field) }}{{ form_widget(mapping.providers) }}{{ form_errors(mapping.providers) }}{{ form_widget(mapping.priority) }}{{ form_errors(mapping.priority) }} + +
    + +
    +
    + +
    + + +
    + {{ form_widget(form.prefetch_details, {'attr': {'class': 'form-check-input'}}) }} + {{ form_label(form.prefetch_details, null, {'label_attr': {'class': 'form-check-label'}}) }} + {{ form_help(form.prefetch_details) }} +
    + + {{ form_widget(form.submit, {'attr': {'class': 'btn btn-primary', 'data-field-mapping-target': 'submitButton'}}) }} +
    + + {{ form_end(form) }} + + {% if search_results is not null %} +
    +

    {% trans %}info_providers.bulk_import.search_results.title{% endtrans %}

    + + {% for part_result in search_results %} + {% set part = part_result.part %} +
    +
    +
    + {{ part.name }} + {% if part_result.errors is not empty %} + {{ part_result.errors|length }} {% trans %}info_providers.bulk_import.errors{% endtrans %} + {% endif %} + {{ part_result.search_results|length }} {% trans %}info_providers.bulk_import.results_found{% endtrans %} +
    +
    +
    + {% if part_result.errors is not empty %} + {% for error in part_result.errors %} + + {% endfor %} + {% endif %} + + {% if part_result.search_results|length > 0 %} +
    + + + + + + + + + + + + + + {% for result in part_result.search_results %} + {% set dto = result.dto %} + {% set localPart = result.localPart %} + + + + + + + + + + {% endfor %} + +
    {% trans %}name.label{% endtrans %}{% trans %}description.label{% endtrans %}{% trans %}manufacturer.label{% endtrans %}{% trans %}info_providers.table.provider.label{% endtrans %}{% trans %}info_providers.bulk_import.source_field{% endtrans %}{% trans %}info_providers.bulk_import.action.label{% endtrans %}
    + + + {% if dto.provider_url is not null %} + {{ dto.name }} + {% else %} + {{ dto.name }} + {% endif %} + {% if dto.mpn is not null %} +
    {{ dto.mpn }} + {% endif %} +
    {{ dto.description }}{{ dto.manufacturer ?? '' }} + {{ info_provider_label(dto.provider_key)|default(dto.provider_key) }} +
    {{ dto.provider_id }} +
    + {{ result.source_field ?? 'unknown' }} + {% if result.source_keyword %} +
    {{ result.source_keyword }} + {% endif %} +
    +
    + {% set updateHref = path('info_providers_update_part', + {'id': part.id, 'providerKey': dto.provider_key, 'providerId': dto.provider_id}) %} + + {% trans %}info_providers.bulk_import.update_part{% endtrans %} + + + {% if localPart is not null %} + + {% trans %}info_providers.bulk_import.view_existing{% endtrans %} + + {% endif %} +
    +
    +
    + {% else %} + + {% endif %} +
    +
    + {% endfor %} + {% endif %} + +
    + +{% endblock %} + diff --git a/templates/info_providers/bulk_import/step2.html.twig b/templates/info_providers/bulk_import/step2.html.twig new file mode 100644 index 00000000..559ca20a --- /dev/null +++ b/templates/info_providers/bulk_import/step2.html.twig @@ -0,0 +1,240 @@ +{% extends "main_card.html.twig" %} + +{% import "info_providers/providers.macro.html.twig" as providers_macro %} +{% import "helper.twig" as helper %} + +{% block title %} + {% trans %}info_providers.bulk_import.step2.title{% endtrans %} +{% endblock %} + +{% block card_title %} + {% trans %}info_providers.bulk_import.step2.title{% endtrans %} + {{ job.displayNameKey|trans(job.displayNameParams) }} - {{ job.formattedTimestamp }} +{% endblock %} + +{% block card_content %} + +
    +
    +
    +
    {{ job.displayNameKey|trans(job.displayNameParams) }} - {{ job.formattedTimestamp }}
    + + {{ job.partCount }} {% trans %}info_providers.bulk_import.parts{% endtrans %} • + {{ job.resultCount }} {% trans %}info_providers.bulk_import.results{% endtrans %} • + {% trans %}info_providers.bulk_import.created_at{% endtrans %}: {{ job.createdAt|date('Y-m-d H:i') }} + +
    +
    + {% if job.isPending %} + {% trans %}info_providers.bulk_import.status.pending{% endtrans %} + {% elseif job.isInProgress %} + {% trans %}info_providers.bulk_import.status.in_progress{% endtrans %} + {% elseif job.isCompleted %} + {% trans %}info_providers.bulk_import.status.completed{% endtrans %} + {% elseif job.isFailed %} + {% trans %}info_providers.bulk_import.status.failed{% endtrans %} + {% endif %} +
    +
    + + +
    +
    +
    +
    Progress
    + {{ job.completedPartsCount }} / {{ job.partCount }} completed +
    +
    +
    +
    +
    +
    + + {{ job.completedPartsCount }} {% trans %}info_providers.bulk_import.completed{% endtrans %} • + {{ job.skippedPartsCount }} {% trans %}info_providers.bulk_import.skipped{% endtrans %} + + {{ job.progressPercentage }}% +
    +
    +
    + + + + + +
    +
    +
    +
    +
    {% trans %}info_providers.bulk_import.research.title{% endtrans %}
    + {% trans %}info_providers.bulk_import.research.description{% endtrans %} +
    +
    + +
    +
    +
    +
    + + {% for part_result in search_results %} + {# @var part_result \App\Services\InfoProviderSystem\DTOs\BulkSearchPartResultsDTO #} + + {% set part = part_result.part %} + {% set isCompleted = job.isPartCompleted(part.id) %} + {% set isSkipped = job.isPartSkipped(part.id) %} +
    +
    +
    +
    + + {{ part.name }} + + {% if isCompleted %} + + {% trans %}info_providers.bulk_import.completed{% endtrans %} + + {% elseif isSkipped %} + + {% trans %}info_providers.bulk_import.skipped{% endtrans %} + + {% endif %} + {% if part_result.errors is not empty %} + {% trans with {'%count%': part_result.errors|length} %}info_providers.bulk_import.errors{% endtrans %} + {% endif %} + {% trans with {'%count%': part_result.searchResults|length} %}info_providers.bulk_import.results_found{% endtrans %} +
    +
    +
    + + {% if not isCompleted and not isSkipped %} + + + {% elseif isCompleted %} + + {% elseif isSkipped %} + + {% endif %} +
    +
    +
    + {% if part_result.errors is not empty %} + {% for error in part_result.errors %} + + {% endfor %} + {% endif %} + + {% if part_result.searchResults|length > 0 %} +
    + + + + + + + + + + + + + + {% for result in part_result.searchResults %} + {# @var result \App\Services\InfoProviderSystem\DTOs\BulkSearchPartResultDTO #} + {% set dto = result.searchResult %} + {% set localPart = result.localPart %} + + + + + + + + + + {% endfor %} + +
    {% trans %}name.label{% endtrans %}{% trans %}description.label{% endtrans %}{% trans %}manufacturer.label{% endtrans %}{% trans %}info_providers.table.provider.label{% endtrans %}{% trans %}info_providers.bulk_import.source_field{% endtrans %}{% trans %}info_providers.bulk_import.action.label{% endtrans %}
    + + + {% if dto.provider_url is not null %} + {{ dto.name }} + {% else %} + {{ dto.name }} + {% endif %} + {% if dto.mpn is not null %} +
    {{ dto.mpn }} + {% endif %} +
    {{ dto.description }}{{ dto.manufacturer ?? '' }} + {{ info_provider_label(dto.provider_key)|default(dto.provider_key) }} +
    {{ dto.provider_id }} +
    + {{ result.sourceField ?? 'unknown' }} + {% if result.sourceKeyword %} +
    {{ result.sourceKeyword }} + {% endif %} +
    +
    + {% set updateHref = path('info_providers_update_part', + {'id': part.id, 'providerKey': dto.provider_key, 'providerId': dto.provider_id}) ~ '?jobId=' ~ job.id %} + + {% trans %}info_providers.bulk_import.update_part{% endtrans %} + +
    +
    +
    + {% else %} + + {% endif %} +
    +
    + {% endfor %} + +
    +{% endblock %} + diff --git a/templates/info_providers/providers.macro.html.twig b/templates/info_providers/providers.macro.html.twig index 7304806a..bf530ebd 100644 --- a/templates/info_providers/providers.macro.html.twig +++ b/templates/info_providers/providers.macro.html.twig @@ -13,7 +13,6 @@ {% else %} {{ provider.providerInfo.name | trans }} {% endif %} -
    {% if provider.providerInfo.description is defined and provider.providerInfo.description is not null %} @@ -23,6 +22,11 @@
    + {% if provider.providerInfo.settings_class is defined %} + + {% endif %} {% for capability in provider.capabilities %} {# @var capability \App\Services\InfoProviderSystem\Providers\ProviderCapabilities #} @@ -52,4 +56,4 @@ {% endfor %} -{% endmacro %} \ No newline at end of file +{% endmacro %} diff --git a/templates/info_providers/search/part_search.html.twig b/templates/info_providers/search/part_search.html.twig index c28235c7..3d741c34 100644 --- a/templates/info_providers/search/part_search.html.twig +++ b/templates/info_providers/search/part_search.html.twig @@ -3,16 +3,25 @@ {% import "info_providers/providers.macro.html.twig" as providers_macro %} {% import "helper.twig" as helper %} -{% block title %}{% trans %}info_providers.search.title{% endtrans %}{% endblock %} +{% block title %} + {% if update_target %} + {% trans %}info_providers.update_part.title{% endtrans %} + {% else %} + {% trans %}info_providers.search.title{% endtrans %} + {% endif %} +{% endblock %} {% block card_title %} - {% trans %}info_providers.search.title{% endtrans %} + {% if update_target %} {# If update_target is set, we update an existing part #} + {% trans %}info_providers.update_part.title{% endtrans %}: + {{ update_target.name }} + {% else %} {# Create a fresh part #} + {% trans %}info_providers.search.title{% endtrans %} + {% endif %} {% endblock %} {% block card_content %} - - {{ form_start(form) }} {{ form_row(form.keyword) }} @@ -29,73 +38,133 @@ {{ form_end(form) }} {% if results is not null %} - - - - - - - - - - - - - - {% for result in results %} + + {% if results|length > 0 %} + {% trans with {'%number%': results|length} %}info_providers.search.number_of_results{% endtrans %}: + +
    {% trans %}name.label{% endtrans %} / {% trans %}part.table.mpn{% endtrans %}{% trans %}description.label{% endtrans %} / {% trans %}category.label{% endtrans %}{% trans %}manufacturer.label{% endtrans %} / {% trans %}footprint.label{% endtrans %}{% trans %}part.table.manufacturingStatus{% endtrans %}{% trans %}info_providers.table.provider.label{% endtrans %}
    + - - - - - - + + + + + + + - {% endfor %} + + + {% for result in results %} + {% set dto = result["dto"] %} + {# @var App\Entity\Parts\Part localPart #} + {% set localPart = result["localPart"] %} - -
    - - - {% if result.provider_url is not null %} - {{ result.name }} - {% else %} - {{ result.name }} - {% endif %} - - {% if result.mpn is not null %} -
    - {{ result.mpn }} - {% endif %} -
    - {{ result.description }} - {% if result.category is not null %} -
    - {{ result.category }} - {% endif %} -
    - {{ result.manufacturer ?? '' }} - {% if result.footprint is not null %} -
    - {{ result.footprint }} - {% endif %} -
    {{ helper.m_status_to_badge(result.manufacturing_status) }} - {% if result.provider_url %} - - {{ info_provider_label(result.provider_key)|default(result.provider_key) }} - - {% else %} - {{ info_provider_label(result.provider_key)|default(result.provider_key) }} - {% endif %} -
    - {{ result.provider_id }} -
    - - - - {% trans %}name.label{% endtrans %} / {% trans %}part.table.mpn{% endtrans %}{% trans %}description.label{% endtrans %} / {% trans %}category.label{% endtrans %}{% trans %}manufacturer.label{% endtrans %} / {% trans %}footprint.label{% endtrans %}{% trans %}part.table.manufacturingStatus{% endtrans %}{% trans %}info_providers.table.provider.label{% endtrans %}
    + + + + + + {% if dto.provider_url is not null %} + {{ dto.name }} + {% else %} + {{ dto.name }} + {% endif %} + + {% if dto.mpn is not null %} +
    + {{ dto.mpn }} + {% endif %} + {% if result["localPart"] is not null %} + + {% endif %} + + + {{ dto.description }} + {% if dto.category is not null %} +
    + {{ dto.category }} + {% endif %} + + + {{ dto.manufacturer ?? '' }} + {% if dto.footprint is not null %} +
    + {{ dto.footprint }} + {% endif %} + + {{ helper.m_status_to_badge(dto.manufacturing_status) }} + + {% if dto.provider_url %} + + {{ info_provider_label(dto.provider_key)|default(dto.provider_key) }} + + {% else %} + {{ info_provider_label(dto.provider_key)|default(dto.provider_key) }} + {% endif %} +
    + {{ dto.provider_id }} + + + + {% if update_target %} {# We update an existing part #} + {% set href = path('info_providers_update_part', + {'providerKey': dto.provider_key, 'providerId': dto.provider_id, 'id': update_target.iD}) %} + {% else %} {# Create a fresh part #} + {% set href = path('info_providers_create_part', + {'providerKey': dto.provider_key, 'providerId': dto.provider_id}) %} + {% endif %} + + {# If we have no local part, then we can just show the create button #} + {% if localPart is null %} + + + + {% else %} {# Otherwise add a button group with all three buttons #} + + + {% trans %}info_providers.search.existing_part_found.short{% endtrans %} + + + + + {% endif %} + + + {% endfor %} + + + + {% else %} + + {% endif %} {% endif %} {% endblock %} diff --git a/templates/info_providers/settings/provider_settings.html.twig b/templates/info_providers/settings/provider_settings.html.twig new file mode 100644 index 00000000..1876c2eb --- /dev/null +++ b/templates/info_providers/settings/provider_settings.html.twig @@ -0,0 +1,31 @@ +{% extends "main_card.html.twig" %} +{% macro genId(widget) %}{{ widget.vars.full_name }}{% endmacro %} + +{% form_theme form "form/settings_form.html.twig" %} + +{% block title %}{% trans %}info_providers.settings.title{% endtrans %}: {{ info_provider_info.name }}{% endblock %} + +{% block card_title %} {% trans %}info_providers.settings.title{% endtrans %}: {{ info_provider_info.name }}{% endblock %} + +{% block card_content %} +
    +

    + {% if info_provider_info.url %} + {{ info_provider_info.name }} + {% else %} + {{ info_provider_info.name }} + {% endif %} +

    + {% if info_provider_info.description %} +

    {{ info_provider_info.description }}

    + {% endif %} +
    + + {{ form_start(form) }} +
    +
    + {{ form_help(form) }} +
    +
    + {{ form_end(form) }} +{% endblock %} diff --git a/templates/label_system/dialog.html.twig b/templates/label_system/dialog.html.twig index a845d12e..b9149aa3 100644 --- a/templates/label_system/dialog.html.twig +++ b/templates/label_system/dialog.html.twig @@ -10,6 +10,9 @@ {% block card_content %} {{ form_start(form) }} + {# Default submit to use when pressing enter. #} + +
    + + {% if is_granted("@labels.read_profiles") %} + + {% endif %} + + {% if form.update_profile is defined %} + {{ form_row(form.update_profile) }} + {% endif %} + +
    +
    +
    + {{ form_widget(form.save_profile_name) }} + {{ form_widget(form.save_profile) }} +
    + {{ form_errors(form.save_profile_name) }} +
    +
    +
    @@ -114,4 +140,4 @@
    {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/label_system/labels/base_label.html.twig b/templates/label_system/labels/base_label.html.twig index 494b99e4..f873f4c2 100644 --- a/templates/label_system/labels/base_label.html.twig +++ b/templates/label_system/labels/base_label.html.twig @@ -1,9 +1,11 @@ +{% import "vars.macro.twig" as vars %} + {{ meta_title }} - +