diff --git a/VERSION b/VERSION index c043eea7..ccbccc3d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2.1 +2.2.0 diff --git a/assets/controllers/elements/attachment_autocomplete_controller.js b/assets/controllers/elements/attachment_autocomplete_controller.js index 94b01136..0175b284 100644 --- a/assets/controllers/elements/attachment_autocomplete_controller.js +++ b/assets/controllers/elements/attachment_autocomplete_controller.js @@ -34,11 +34,6 @@ export default class extends Controller { connect() { - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - let settings = { persistent: false, create: true, @@ -47,7 +42,7 @@ export default class extends Controller { selectOnTab: true, //This a an ugly solution to disable the delimiter parsing of the TomSelect plugin delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING', - dropdownParent: dropdownParent, + dropdownParent: 'body', render: { item: (data, escape) => { return '' + escape(data.label) + ''; diff --git a/assets/controllers/elements/ckeditor_controller.js b/assets/controllers/elements/ckeditor_controller.js index 46e78fd5..5612ca05 100644 --- a/assets/controllers/elements/ckeditor_controller.js +++ b/assets/controllers/elements/ckeditor_controller.js @@ -31,8 +31,7 @@ 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$/ + /\.js$/ ); function loadTranslation(language) { @@ -87,8 +86,7 @@ export default class extends Controller { //Load translations if not english let translations = loadTranslation(language); if (translations) { - //Keep existing translations (e.g. from other plugins), if any - config.translations = [window.CKEDITOR_TRANSLATIONS, translations]; + config.translations = [translations]; } const watchdog = new EditorWatchdog(); diff --git a/assets/controllers/elements/part_select_controller.js b/assets/controllers/elements/part_select_controller.js index 8a4e19b8..0658f4b4 100644 --- a/assets/controllers/elements/part_select_controller.js +++ b/assets/controllers/elements/part_select_controller.js @@ -10,19 +10,13 @@ export default class extends Controller { connect() { - //Check if tomselect is inside an modal and do not attach the dropdown to body in that case (as it breaks the modal) - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - let settings = { allowEmptyOption: true, plugins: ['dropdown_input'], searchField: ["name", "description", "category", "footprint"], valueField: "id", labelField: "name", - dropdownParent: dropdownParent, + dropdownParent: 'body', preload: "focus", render: { item: (data, escape) => { diff --git a/assets/controllers/elements/select_controller.js b/assets/controllers/elements/select_controller.js index d70e588c..f933731a 100644 --- a/assets/controllers/elements/select_controller.js +++ b/assets/controllers/elements/select_controller.js @@ -38,17 +38,13 @@ export default class extends Controller { this._emptyMessage = this.element.getAttribute('title'); } - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } let settings = { plugins: ["clear_button"], allowEmptyOption: true, selectOnTab: true, maxOptions: null, - dropdownParent: dropdownParent, + dropdownParent: 'body', render: { item: this.renderItem.bind(this), diff --git a/assets/controllers/elements/select_multiple_controller.js b/assets/controllers/elements/select_multiple_controller.js index 17e85fae..daa6b0a1 100644 --- a/assets/controllers/elements/select_multiple_controller.js +++ b/assets/controllers/elements/select_multiple_controller.js @@ -26,15 +26,10 @@ export default class extends Controller { _tomSelect; connect() { - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - this._tomSelect = new TomSelect(this.element, { maxItems: 1000, allowEmptyOption: true, - dropdownParent: dropdownParent, + dropdownParent: 'body', plugins: ['remove_button'], }); } diff --git a/assets/controllers/elements/static_file_autocomplete_controller.js b/assets/controllers/elements/static_file_autocomplete_controller.js index 9703c618..0421a26d 100644 --- a/assets/controllers/elements/static_file_autocomplete_controller.js +++ b/assets/controllers/elements/static_file_autocomplete_controller.js @@ -40,11 +40,6 @@ export default class extends Controller { connect() { - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - let settings = { persistent: false, create: true, @@ -55,7 +50,7 @@ export default class extends Controller { valueField: 'text', searchField: 'text', orderField: 'text', - dropdownParent: dropdownParent, + dropdownParent: 'body', //This a an ugly solution to disable the delimiter parsing of the TomSelect plugin delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING', diff --git a/assets/controllers/elements/structural_entity_select_controller.js b/assets/controllers/elements/structural_entity_select_controller.js index 4b220d5b..5c6f9490 100644 --- a/assets/controllers/elements/structural_entity_select_controller.js +++ b/assets/controllers/elements/structural_entity_select_controller.js @@ -40,10 +40,7 @@ export default class extends Controller { const allowAdd = this.element.getAttribute("data-allow-add") === "true"; const addHint = this.element.getAttribute("data-add-hint") ?? ""; - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } + let settings = { @@ -57,7 +54,7 @@ export default class extends Controller { maxItems: 1, delimiter: "$$VERY_LONG_DELIMITER_THAT_SHOULD_NEVER_APPEAR$$", splitOn: null, - dropdownParent: dropdownParent, + dropdownParent: 'body', searchField: [ {field: "text", weight : 2}, diff --git a/assets/controllers/elements/tagsinput_controller.js b/assets/controllers/elements/tagsinput_controller.js index 14725227..53bf7608 100644 --- a/assets/controllers/elements/tagsinput_controller.js +++ b/assets/controllers/elements/tagsinput_controller.js @@ -33,11 +33,6 @@ export default class extends Controller { _tomSelect; connect() { - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - let settings = { plugins: { remove_button:{}, @@ -48,7 +43,7 @@ export default class extends Controller { selectOnTab: true, createOnBlur: true, create: true, - dropdownParent: dropdownParent, + dropdownParent: 'body', }; if(this.element.dataset.autocomplete) { diff --git a/composer.lock b/composer.lock index 72e83e0f..5fa11c2d 100644 --- a/composer.lock +++ b/composer.lock @@ -2909,16 +2909,16 @@ }, { "name": "doctrine/data-fixtures", - "version": "2.2.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/data-fixtures.git", - "reference": "7a615ba135e45d67674bb623d90f34f6c7b6bd97" + "reference": "f161e20f04ba5440a09330e156b40f04dd70d47f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/7a615ba135e45d67674bb623d90f34f6c7b6bd97", - "reference": "7a615ba135e45d67674bb623d90f34f6c7b6bd97", + "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/f161e20f04ba5440a09330e156b40f04dd70d47f", + "reference": "f161e20f04ba5440a09330e156b40f04dd70d47f", "shasum": "" }, "require": { @@ -2932,14 +2932,14 @@ "doctrine/phpcr-odm": "<1.3.0" }, "require-dev": { - "doctrine/coding-standard": "^14", + "doctrine/coding-standard": "^13", "doctrine/dbal": "^3.5 || ^4", "doctrine/mongodb-odm": "^1.3.0 || ^2.0.0", "doctrine/orm": "^2.14 || ^3", "ext-sqlite3": "*", "fig/log-test": "^1", - "phpstan/phpstan": "2.1.31", - "phpunit/phpunit": "10.5.45 || 12.4.0", + "phpstan/phpstan": "2.1.17", + "phpunit/phpunit": "10.5.45", "symfony/cache": "^6.4 || ^7", "symfony/var-exporter": "^6.4 || ^7" }, @@ -2972,7 +2972,7 @@ ], "support": { "issues": "https://github.com/doctrine/data-fixtures/issues", - "source": "https://github.com/doctrine/data-fixtures/tree/2.2.0" + "source": "https://github.com/doctrine/data-fixtures/tree/2.1.0" }, "funding": [ { @@ -2988,7 +2988,7 @@ "type": "tidelift" } ], - "time": "2025-10-17T20:06:20+00:00" + "time": "2025-07-08T17:48:20+00:00" }, { "name": "doctrine/dbal", @@ -5503,22 +5503,22 @@ }, { "name": "lcobucci/jwt", - "version": "5.6.0", + "version": "5.5.0", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e" + "reference": "a835af59b030d3f2967725697cf88300f579088e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e", - "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a835af59b030d3f2967725697cf88300f579088e", + "reference": "a835af59b030d3f2967725697cf88300f579088e", "shasum": "" }, "require": { "ext-openssl": "*", "ext-sodium": "*", - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", "psr/clock": "^1.0" }, "require-dev": { @@ -5560,7 +5560,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/5.6.0" + "source": "https://github.com/lcobucci/jwt/tree/5.5.0" }, "funding": [ { @@ -5572,7 +5572,7 @@ "type": "patreon" } ], - "time": "2025-10-17T11:30:53+00:00" + "time": "2025-01-26T21:29:45+00:00" }, { "name": "league/commonmark", @@ -19047,12 +19047,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "7a8f128281289412092c450a5eb3df5cabbc89e1" + "reference": "b6e429a7bb297ab3b78ea05021ccd3fe4568dd5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/7a8f128281289412092c450a5eb3df5cabbc89e1", - "reference": "7a8f128281289412092c450a5eb3df5cabbc89e1", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/b6e429a7bb297ab3b78ea05021ccd3fe4568dd5e", + "reference": "b6e429a7bb297ab3b78ea05021ccd3fe4568dd5e", "shasum": "" }, "conflict": { @@ -19267,7 +19267,7 @@ "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1-dev", "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1-dev|>=5.4,<5.4.11.1-dev|>=2017.12,<2017.12.0.1-dev", "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", - "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.39|>=3.3,<3.3.39", + "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.38|>=3.3,<3.3.39", "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1|>=5.3.0.0-beta1,<5.3.5", "ezsystems/ezplatform-graphql": ">=1.0.0.0-RC1-dev,<1.0.13|>=2.0.0.0-beta1,<2.3.12", "ezsystems/ezplatform-http-cache": "<2.3.16", @@ -19361,15 +19361,15 @@ "hov/jobfair": "<1.0.13|>=2,<2.0.2", "httpsoft/http-message": "<1.0.12", "hyn/multi-tenant": ">=5.6,<5.7.2", - "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6,<4.6.25|>=5,<5.0.3", + "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6,<4.6.21", "ibexa/admin-ui-assets": ">=4.6.0.0-alpha1,<4.6.21", "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3|>=4.5,<4.5.6|>=4.6,<4.6.2", - "ibexa/fieldtype-richtext": ">=4.6,<4.6.25|>=5,<5.0.3", + "ibexa/fieldtype-richtext": ">=4.6,<4.6.21", "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", "ibexa/http-cache": ">=4.6,<4.6.14", "ibexa/post-install": "<1.0.16|>=4.6,<4.6.14", "ibexa/solr": ">=4.5,<4.5.4", - "ibexa/user": ">=4,<4.4.3|>=5,<5.0.3", + "ibexa/user": ">=4,<4.4.3", "icecoder/icecoder": "<=8.1", "idno/known": "<=1.3.1", "ilicmiljan/secure-props": ">=1.2,<1.2.2", @@ -20019,7 +20019,7 @@ "type": "tidelift" } ], - "time": "2025-10-17T18:06:27+00:00" + "time": "2025-10-16T20:06:12+00:00" }, { "name": "sebastian/cli-parser", diff --git a/config/parameters.yaml b/config/parameters.yaml index d4fe7581..5b40899d 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -8,7 +8,7 @@ parameters: # This is used as workaround for places where we can not access the settings directly (like the 2FA application names) partdb.title: '%env(string:settings:customization:instanceName)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage) - partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl', 'hu'] # The languages that are shown in user drop down menu + partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] # The languages that are shown in user drop down menu partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails diff --git a/docs/upgrade/1_to_2.md b/docs/upgrade/1_to_2.md index c333136a..f5b3b085 100644 --- a/docs/upgrade/1_to_2.md +++ b/docs/upgrade/1_to_2.md @@ -48,15 +48,14 @@ The upgrade process works very similar to a normal (minor release) upgrade. 1. Make a backup of your existing Part-DB installation, including the database, data directories and the configuration files and `.env.local` file. The `php bin/console partdb:backup` command can help you with this. 2. Pull the v2 version. For git installation you can do this with `git checkout v2.0.0` (or newer version) -3. Remove the `var/cache/` directory inside the Part-DB installation to ensure that no old cache files remain. -4. Run `composer install --no-dev -o` to update the dependencies. -5. Run `yarn install` and `yarn build` to update the frontend assets. -6. Rund `php bin/console doctrine:migrations:migrate` to update the database schema. -7. Clear the cache with `php bin/console cache:clear`. -8. Open your Part-DB instance in the browser and log in as an admin user. -9. Go to the user or group permissions page, and give yourself (and other administrators) the right to change system settings (under "System" and "Configuration"). -10. You can now go to the settings page (under "System" and "Settings") and check if all settings are correct. -11. Parameters which were previously set via environment variables are greyed out and cannot be changed in the web interface. +3. Run `composer install --no-dev -o` to update the dependencies. +4. Run `yarn install` and `yarn build` to update the frontend assets. +5. Rund `php bin/console doctrine:migrations:migrate` to update the database schema. +6. Clear the cache with `php bin/console cache:clear`. +7. Open your Part-DB instance in the browser and log in as an admin user. +8. Go to the user or group permissions page, and give yourself (and other administrators) the right to change system settings (under "System" and "Configuration"). +9. You can now go to the settings page (under "System" and "Settings") and check if all settings are correct. +10. Parameters which were previously set via environment variables are greyed out and cannot be changed in the web interface. If you want to change them, you must migrate them to the settings interface as described below. ### Docker installation @@ -88,15 +87,3 @@ After the migration run successfully, the contents of your environment variables Go through the environment variables listed by the command and remove them from your environment variable configuration (e.g. `.env.local` file or docker compose file), or just comment them out for now. If you want to keep some environment variables, just leave them as they are, they will still work as before, the migration command only affects the settings stored in the database. - - -## Troubleshooting - -### cache:clear fails: You have requested a non-existent parameter "jbtronics.settings.proxy_dir". -If you receive an error like -``` -In App_KernelProdContainer.php line 2839: -You have requested a non-existent parameter "jbtronics.settings.proxy_dir". -``` -when running `php bin/console cache:clear` or `composer install`. You have to manually delete the `var/cache/` -directory inside your Part-DB installation and try again. diff --git a/public/img/calculator/ratio.png b/public/img/calculator/ratio.png new file mode 100644 index 00000000..d6decff3 Binary files /dev/null and b/public/img/calculator/ratio.png differ diff --git a/public/img/calculator/v1.png b/public/img/calculator/v1.png new file mode 100644 index 00000000..c98d3ad4 Binary files /dev/null and b/public/img/calculator/v1.png differ diff --git a/public/img/calculator/v2.png b/public/img/calculator/v2.png new file mode 100644 index 00000000..081386fe Binary files /dev/null and b/public/img/calculator/v2.png differ diff --git a/public/img/labels/100.png b/public/img/labels/100.png new file mode 100644 index 00000000..f68a23a9 Binary files /dev/null and b/public/img/labels/100.png differ diff --git a/public/img/labels/1001.png b/public/img/labels/1001.png new file mode 100644 index 00000000..c87e4ceb Binary files /dev/null and b/public/img/labels/1001.png differ diff --git a/public/img/labels/1002.png b/public/img/labels/1002.png new file mode 100644 index 00000000..68b6594c Binary files /dev/null and b/public/img/labels/1002.png differ diff --git a/public/img/labels/1003.png b/public/img/labels/1003.png new file mode 100644 index 00000000..2abbd616 Binary files /dev/null and b/public/img/labels/1003.png differ diff --git a/public/img/labels/100R.png b/public/img/labels/100R.png new file mode 100644 index 00000000..34fb8fa8 Binary files /dev/null and b/public/img/labels/100R.png differ diff --git a/public/img/labels/101.png b/public/img/labels/101.png new file mode 100644 index 00000000..dd07aa39 Binary files /dev/null and b/public/img/labels/101.png differ diff --git a/public/img/labels/102.png b/public/img/labels/102.png new file mode 100644 index 00000000..a54e16b7 Binary files /dev/null and b/public/img/labels/102.png differ diff --git a/public/img/labels/10R2.png b/public/img/labels/10R2.png new file mode 100644 index 00000000..2b57f7d4 Binary files /dev/null and b/public/img/labels/10R2.png differ diff --git a/public/img/labels/220.png b/public/img/labels/220.png new file mode 100644 index 00000000..28ede43d Binary files /dev/null and b/public/img/labels/220.png differ diff --git a/public/img/labels/221K.png b/public/img/labels/221K.png new file mode 100644 index 00000000..1dbb0c61 Binary files /dev/null and b/public/img/labels/221K.png differ diff --git a/public/img/labels/246-20.png b/public/img/labels/246-20.png new file mode 100644 index 00000000..590f7c5d Binary files /dev/null and b/public/img/labels/246-20.png differ diff --git a/public/img/labels/3F3.png b/public/img/labels/3F3.png new file mode 100644 index 00000000..ce85ae97 Binary files /dev/null and b/public/img/labels/3F3.png differ diff --git a/public/img/labels/R10.png b/public/img/labels/R10.png new file mode 100644 index 00000000..60a90182 Binary files /dev/null and b/public/img/labels/R10.png differ diff --git a/public/img/labels/template-c-elko-alu.png b/public/img/labels/template-c-elko-alu.png new file mode 100644 index 00000000..24d68d91 Binary files /dev/null and b/public/img/labels/template-c-elko-alu.png differ diff --git a/public/img/labels/template-c-elko.png b/public/img/labels/template-c-elko.png new file mode 100644 index 00000000..97e3c1ef Binary files /dev/null and b/public/img/labels/template-c-elko.png differ diff --git a/public/img/labels/template-c-tantal.png b/public/img/labels/template-c-tantal.png new file mode 100644 index 00000000..3e49efee Binary files /dev/null and b/public/img/labels/template-c-tantal.png differ diff --git a/public/img/labels/template-l.png b/public/img/labels/template-l.png new file mode 100644 index 00000000..7e5afd92 Binary files /dev/null and b/public/img/labels/template-l.png differ diff --git a/public/img/labels/template-r.png b/public/img/labels/template-r.png new file mode 100644 index 00000000..554d2a08 Binary files /dev/null and b/public/img/labels/template-r.png differ diff --git a/public/img/partdb/alldatasheet.png b/public/img/partdb/alldatasheet.png new file mode 100644 index 00000000..d7c1d40f Binary files /dev/null and b/public/img/partdb/alldatasheet.png differ diff --git a/public/img/partdb/dc.png b/public/img/partdb/dc.png new file mode 100644 index 00000000..4a9403af Binary files /dev/null and b/public/img/partdb/dc.png differ diff --git a/public/img/partdb/dummytn.png b/public/img/partdb/dummytn.png new file mode 100644 index 00000000..e63c9248 Binary files /dev/null and b/public/img/partdb/dummytn.png differ diff --git a/public/img/partdb/favicon.ico b/public/img/partdb/favicon.ico new file mode 100644 index 00000000..1d838794 Binary files /dev/null and b/public/img/partdb/favicon.ico differ diff --git a/public/img/partdb/file_all.svg b/public/img/partdb/file_all.svg new file mode 100644 index 00000000..bb4b4248 --- /dev/null +++ b/public/img/partdb/file_all.svg @@ -0,0 +1,131 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/img/partdb/file_dc.svg b/public/img/partdb/file_dc.svg new file mode 100644 index 00000000..f0039881 --- /dev/null +++ b/public/img/partdb/file_dc.svg @@ -0,0 +1,90 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + DC + diff --git a/public/img/partdb/file_google.svg b/public/img/partdb/file_google.svg new file mode 100644 index 00000000..20ea96bf --- /dev/null +++ b/public/img/partdb/file_google.svg @@ -0,0 +1,5 @@ + + +google + + diff --git a/public/img/partdb/file_octo.svg b/public/img/partdb/file_octo.svg new file mode 100644 index 00000000..307439a5 --- /dev/null +++ b/public/img/partdb/file_octo.svg @@ -0,0 +1,5 @@ + + +cog + + diff --git a/public/img/partdb/file_reichelt.svg b/public/img/partdb/file_reichelt.svg new file mode 100644 index 00000000..488dafaa --- /dev/null +++ b/public/img/partdb/file_reichelt.svg @@ -0,0 +1,98 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/public/img/partdb/help.png b/public/img/partdb/help.png new file mode 100644 index 00000000..7cb04978 Binary files /dev/null and b/public/img/partdb/help.png differ diff --git a/public/img/partdb/partdb.png b/public/img/partdb/partdb.png new file mode 100644 index 00000000..53f51afb Binary files /dev/null and b/public/img/partdb/partdb.png differ diff --git a/public/img/partdb/reichelt.png b/public/img/partdb/reichelt.png new file mode 100644 index 00000000..fcfcfd49 Binary files /dev/null and b/public/img/partdb/reichelt.png differ diff --git a/public/img/partdb/template-pdf.png b/public/img/partdb/template-pdf.png new file mode 100644 index 00000000..211bf5a4 Binary files /dev/null and b/public/img/partdb/template-pdf.png differ diff --git a/src/Controller/InfoProviderController.php b/src/Controller/InfoProviderController.php index b79c307c..dae8213e 100644 --- a/src/Controller/InfoProviderController.php +++ b/src/Controller/InfoProviderController.php @@ -25,7 +25,6 @@ namespace App\Controller; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Part; -use App\Exceptions\OAuthReconnectRequiredException; use App\Form\InfoProviderSystem\PartSearchType; use App\Services\InfoProviderSystem\ExistingPartFinder; use App\Services\InfoProviderSystem\PartInfoRetriever; @@ -176,11 +175,8 @@ class InfoProviderController extends AbstractController $this->addFlash('error',$e->getMessage()); //Log the exception $exceptionLogger->error('Error during info provider search: ' . $e->getMessage(), ['exception' => $e]); - } catch (OAuthReconnectRequiredException $e) { - $this->addFlash('error', t('info_providers.search.error.oauth_reconnect', ['%provider%' => $e->getProviderName()])); } - // modify the array to an array of arrays that has a field for a matching local Part // the advantage to use that format even when we don't look for local parts is that we // always work with the same interface diff --git a/src/Controller/PartListsController.php b/src/Controller/PartListsController.php index 808b0c5d..8ea218f4 100644 --- a/src/Controller/PartListsController.php +++ b/src/Controller/PartListsController.php @@ -36,7 +36,6 @@ use App\Exceptions\InvalidRegexException; use App\Form\Filters\PartFilterType; use App\Services\Parts\PartsTableActionHandler; use App\Services\Trees\NodesListBuilder; -use App\Settings\BehaviorSettings\SidebarSettings; use App\Settings\BehaviorSettings\TableSettings; use Doctrine\DBAL\Exception\DriverException; use Doctrine\ORM\EntityManagerInterface; @@ -57,21 +56,11 @@ class PartListsController extends AbstractController private readonly NodesListBuilder $nodesListBuilder, private readonly DataTableFactory $dataTableFactory, private readonly TranslatorInterface $translator, - private readonly TableSettings $tableSettings, - private readonly SidebarSettings $sidebarSettings, + private readonly TableSettings $tableSettings ) { } - /** - * Gets the filter operator to use by default (INCLUDING_CHILDREN or =) - * @return string - */ - private function getFilterOperator(): string - { - return $this->sidebarSettings->dataStructureNodesTableIncludeChildren ? 'INCLUDING_CHILDREN' : '='; - } - #[Route(path: '/table/action', name: 'table_action', methods: ['POST'])] public function tableAction(Request $request, PartsTableActionHandler $actionHandler): Response { @@ -214,7 +203,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/category_list.html.twig', function (PartFilter $filter) use ($category) { - $filter->category->setOperator($this->getFilterOperator())->setValue($category); + $filter->category->setOperator('INCLUDING_CHILDREN')->setValue($category); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('category')->get('value')); }, [ @@ -232,7 +221,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/footprint_list.html.twig', function (PartFilter $filter) use ($footprint) { - $filter->footprint->setOperator($this->getFilterOperator())->setValue($footprint); + $filter->footprint->setOperator('INCLUDING_CHILDREN')->setValue($footprint); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('footprint')->get('value')); }, [ @@ -250,7 +239,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/manufacturer_list.html.twig', function (PartFilter $filter) use ($manufacturer) { - $filter->manufacturer->setOperator($this->getFilterOperator())->setValue($manufacturer); + $filter->manufacturer->setOperator('INCLUDING_CHILDREN')->setValue($manufacturer); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('manufacturer')->get('value')); }, [ @@ -268,7 +257,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/store_location_list.html.twig', function (PartFilter $filter) use ($storelocation) { - $filter->storelocation->setOperator($this->getFilterOperator())->setValue($storelocation); + $filter->storelocation->setOperator('INCLUDING_CHILDREN')->setValue($storelocation); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('storelocation')->get('value')); }, [ @@ -286,7 +275,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/supplier_list.html.twig', function (PartFilter $filter) use ($supplier) { - $filter->supplier->setOperator($this->getFilterOperator())->setValue($supplier); + $filter->supplier->setOperator('INCLUDING_CHILDREN')->setValue($supplier); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('supplier')->get('value')); }, [ diff --git a/src/DataTables/Filters/Constraints/TextConstraint.php b/src/DataTables/Filters/Constraints/TextConstraint.php index c6a6fe19..31b12a5e 100644 --- a/src/DataTables/Filters/Constraints/TextConstraint.php +++ b/src/DataTables/Filters/Constraints/TextConstraint.php @@ -96,15 +96,14 @@ class TextConstraint extends AbstractConstraint //The CONTAINS, LIKE, STARTS and ENDS operators use the LIKE operator, but we have to build the value string differently $like_value = null; - $escaped_value = str_replace(['%', '_'], ['\%', '\_'], $this->value); if ($this->operator === 'LIKE') { - $like_value = $this->value; //Here we do not escape anything, as the user may provide % and _ wildcards + $like_value = $this->value; } elseif ($this->operator === 'STARTS') { - $like_value = $escaped_value . '%'; + $like_value = $this->value . '%'; } elseif ($this->operator === 'ENDS') { - $like_value = '%' . $escaped_value; + $like_value = '%' . $this->value; } elseif ($this->operator === 'CONTAINS') { - $like_value = '%' . $escaped_value . '%'; + $like_value = '%' . $this->value . '%'; } if ($like_value !== null) { diff --git a/src/DataTables/Filters/PartSearchFilter.php b/src/DataTables/Filters/PartSearchFilter.php index aa8c20f4..60832b26 100644 --- a/src/DataTables/Filters/PartSearchFilter.php +++ b/src/DataTables/Filters/PartSearchFilter.php @@ -144,8 +144,6 @@ class PartSearchFilter implements FilterInterface if ($this->regex) { $queryBuilder->setParameter('search_query', $this->keyword); } else { - //Escape % and _ characters in the keyword - $this->keyword = str_replace(['%', '_'], ['\%', '\_'], $this->keyword); $queryBuilder->setParameter('search_query', '%' . $this->keyword . '%'); } } diff --git a/src/Doctrine/Functions/ILike.php b/src/Doctrine/Functions/ILike.php index ff2d2163..5246220a 100644 --- a/src/Doctrine/Functions/ILike.php +++ b/src/Doctrine/Functions/ILike.php @@ -56,6 +56,7 @@ class ILike extends FunctionNode { $platform = $sqlWalker->getConnection()->getDatabasePlatform(); + // if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SQLitePlatform) { $operator = 'LIKE'; } elseif ($platform instanceof PostgreSQLPlatform) { @@ -65,12 +66,6 @@ class ILike extends FunctionNode throw new \RuntimeException('Platform ' . gettype($platform) . ' does not support case insensitive like expressions.'); } - $escape = ""; - if ($platform instanceof SQLitePlatform) { - //SQLite needs ESCAPE explicitly defined backslash as escape character - $escape = " ESCAPE '\\'"; - } - - return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->expr->dispatch($sqlWalker) . $escape . ')'; + return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->expr->dispatch($sqlWalker) . ')'; } -} +} \ No newline at end of file diff --git a/src/Exceptions/OAuthReconnectRequiredException.php b/src/Exceptions/OAuthReconnectRequiredException.php deleted file mode 100644 index 97abb19f..00000000 --- a/src/Exceptions/OAuthReconnectRequiredException.php +++ /dev/null @@ -1,48 +0,0 @@ -. - */ - -declare(strict_types=1); - - -namespace App\Exceptions; - -use Throwable; - -class OAuthReconnectRequiredException extends \RuntimeException -{ - private string $providerName = "unknown"; - - public function __construct(string $message = "You need to reconnect the OAuth connection for this provider!", int $code = 0, ?Throwable $previous = null) - { - parent::__construct($message, $code, $previous); - } - - public static function forProvider(string $providerName): self - { - $exception = new self("You need to reconnect the OAuth connection for the provider '$providerName'!"); - $exception->providerName = $providerName; - return $exception; - } - - public function getProviderName(): string - { - return $this->providerName; - } -} diff --git a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php index a655a0df..d09f1d05 100644 --- a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php +++ b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php @@ -221,7 +221,7 @@ final class DTOtoEntityConverter $attachment = $this->convertFile($image, $image_type); $attachments_grouped[$attachment->getName()][] = $attachment; - if (count($attachments_grouped[$attachment->getName()]) > 1) { + if (count($attachments_grouped[$attachment->getName()] ?? []) > 1) { $attachment->setName($attachment->getName() . ' (' . (count($attachments_grouped[$attachment->getName()]) + 1) . ')'); } @@ -236,7 +236,7 @@ final class DTOtoEntityConverter $attachment = $this->convertFile($datasheet, $datasheet_type); $attachments_grouped[$attachment->getName()][] = $attachment; - if (count($attachments_grouped[$attachment->getName()]) > 1) { + if (count($attachments_grouped[$attachment->getName()] ?? []) > 1) { $attachment->setName($attachment->getName() . ' (' . (count($attachments_grouped[$attachment->getName()])) . ')'); } @@ -357,4 +357,4 @@ final class DTOtoEntityConverter return $tmp; } -} +} \ No newline at end of file diff --git a/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php b/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php index 423b5244..51f460e4 100644 --- a/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php +++ b/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php @@ -24,7 +24,6 @@ declare(strict_types=1); namespace App\Services\InfoProviderSystem\Providers; use App\Entity\Parts\ManufacturingStatus; -use App\Exceptions\OAuthReconnectRequiredException; use App\Services\InfoProviderSystem\DTOs\FileDTO; use App\Services\InfoProviderSystem\DTOs\ParameterDTO; use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; @@ -118,22 +117,12 @@ class DigikeyProvider implements InfoProviderInterface ]; //$response = $this->digikeyClient->request('POST', '/Search/v3/Products/Keyword', [ - try { - $response = $this->digikeyClient->request('POST', '/products/v4/search/keyword', [ - 'json' => $request, - 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) - ]); - - $response_array = $response->toArray(); - } catch (\InvalidArgumentException $exception) { - //Check if the exception was caused by an invalid or expired token - if (str_contains($exception->getMessage(), 'access_token')) { - throw OAuthReconnectRequiredException::forProvider($this->getProviderKey()); - } - - throw $exception; - } + $response = $this->digikeyClient->request('POST', '/products/v4/search/keyword', [ + 'json' => $request, + 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) + ]); + $response_array = $response->toArray(); $result = []; @@ -161,18 +150,9 @@ class DigikeyProvider implements InfoProviderInterface public function getDetails(string $id): PartDetailDTO { - try { - $response = $this->digikeyClient->request('GET', '/products/v4/search/' . urlencode($id) . '/productdetails', [ - 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) - ]); - } catch (\InvalidArgumentException $exception) { - //Check if the exception was caused by an invalid or expired token - if (str_contains($exception->getMessage(), 'access_token')) { - throw OAuthReconnectRequiredException::forProvider($this->getProviderKey()); - } - - throw $exception; - } + $response = $this->digikeyClient->request('GET', '/products/v4/search/' . urlencode($id) . '/productdetails', [ + 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) + ]); $response_array = $response->toArray(); $product = $response_array['Product']; diff --git a/src/Services/ProjectSystem/ProjectBuildHelper.php b/src/Services/ProjectSystem/ProjectBuildHelper.php index a541c29d..269c7e4c 100644 --- a/src/Services/ProjectSystem/ProjectBuildHelper.php +++ b/src/Services/ProjectSystem/ProjectBuildHelper.php @@ -31,9 +31,9 @@ use App\Services\Parts\PartLotWithdrawAddHelper; /** * @see \App\Tests\Services\ProjectSystem\ProjectBuildHelperTest */ -final readonly class ProjectBuildHelper +class ProjectBuildHelper { - public function __construct(private PartLotWithdrawAddHelper $withdraw_add_helper) + public function __construct(private readonly PartLotWithdrawAddHelper $withdraw_add_helper) { } @@ -63,35 +63,18 @@ final readonly class ProjectBuildHelper */ public function getMaximumBuildableCount(Project $project): int { - $bom_entries = $project->getBomEntries(); - if ($bom_entries->isEmpty()) { - return 0; - } $maximum_buildable_count = PHP_INT_MAX; - foreach ($bom_entries as $bom_entry) { + foreach ($project->getBomEntries() as $bom_entry) { //Skip BOM entries without a part (as we can not determine that) if (!$bom_entry->isPartBomEntry()) { continue; } + //The maximum buildable count for the whole project is the minimum of all BOM entries $maximum_buildable_count = min($maximum_buildable_count, $this->getMaximumBuildableCountForBOMEntry($bom_entry)); } - return $maximum_buildable_count; - } - /** - * Returns the maximum buildable amount of the given project as string, based on the stock of the used parts in the BOM. - * If the maximum buildable count is infinite, the string '∞' is returned. - * @param Project $project - * @return string - */ - public function getMaximumBuildableCountAsString(Project $project): string - { - $max_count = $this->getMaximumBuildableCount($project); - if ($max_count === PHP_INT_MAX) { - return '∞'; - } - return (string) $max_count; + return $maximum_buildable_count; } /** diff --git a/src/Settings/BehaviorSettings/SidebarSettings.php b/src/Settings/BehaviorSettings/SidebarSettings.php index a1ff6985..1266fa47 100644 --- a/src/Settings/BehaviorSettings/SidebarSettings.php +++ b/src/Settings/BehaviorSettings/SidebarSettings.php @@ -73,11 +73,4 @@ class SidebarSettings */ #[SettingsParameter(label: new TM("settings.behavior.sidebar.rootNodeRedirectsToNewEntity"))] public bool $rootNodeRedirectsToNewEntity = false; - - /** - * @var bool Whether to include child nodes in the data structure nodes table, or only show the selected node's parts. - */ - #[SettingsParameter(label: new TM("settings.behavior.sidebar.data_structure_nodes_table_include_children"), - description: new TM("settings.behavior.sidebar.data_structure_nodes_table_include_children.help"))] - public bool $dataStructureNodesTableIncludeChildren = true; -} +} \ No newline at end of file diff --git a/src/Twig/FormatExtension.php b/src/Twig/FormatExtension.php index 46313aaf..76628ccd 100644 --- a/src/Twig/FormatExtension.php +++ b/src/Twig/FormatExtension.php @@ -82,7 +82,7 @@ final class FormatExtension extends AbstractExtension public function formatBytes(int $bytes, int $precision = 2): string { $size = ['B','kB','MB','GB','TB','PB','EB','ZB','YB']; - $factor = (int) floor((strlen((string) $bytes) - 1) / 3); + $factor = floor((strlen((string) $bytes) - 1) / 3); //We use the real (10 based) SI prefix here return sprintf("%.{$precision}f", $bytes / (1000 ** $factor)) . ' ' . @$size[$factor]; } diff --git a/templates/form/permission_layout.html.twig b/templates/form/permission_layout.html.twig index 747208dd..166147b4 100644 --- a/templates/form/permission_layout.html.twig +++ b/templates/form/permission_layout.html.twig @@ -70,20 +70,18 @@ {% endif %} {% if show_presets %} - {# This hidden field is there to ensure that none of the presets is submitted, if a user presses enter #} -
@@ -112,4 +110,4 @@ {% endfor %}
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/helper.twig b/templates/helper.twig index 66268a96..bd1d2aa7 100644 --- a/templates/helper.twig +++ b/templates/helper.twig @@ -214,11 +214,11 @@ {% endmacro %} {% macro parameters_table(parameters) %} - +
- + @@ -240,4 +240,4 @@ {% else %} {{ datetime|format_datetime }} {% endif %} -{% endmacro %} +{% endmacro %} \ No newline at end of file diff --git a/templates/label_system/dialog.html.twig b/templates/label_system/dialog.html.twig index b9149aa3..037b549e 100644 --- a/templates/label_system/dialog.html.twig +++ b/templates/label_system/dialog.html.twig @@ -10,9 +10,6 @@ {% block card_content %} {{ form_start(form) }} - {# Default submit to use when pressing enter. #} - -
{% trans %}specifications.property{% endtrans %}{% trans %}specifications.symbol{% endtrans %}{% trans %}specifications.symbol{% endtrans %} {% trans %}specifications.value{% endtrans %}